├── .gitignore ├── Common ├── Appearance.swift ├── AsyncLoader.swift ├── BinarySearch.swift ├── DictionaryCache.swift ├── DictionarySettings.swift ├── EntryParser.swift ├── FlickSKK-Common-Bridging-Header.h ├── IOUtil.h ├── IOUtil.mm ├── IdFilter.swift ├── LoadLocalDictionary.swift ├── LocalFile.swift ├── NumberFilter.swift ├── NumberFormatter.swift ├── SKKDictionary.swift ├── SKKDictionaryEntry.swift ├── SKKDictionaryFile.swift ├── SKKFilter.swift ├── SKKLocalDictionaryFile.swift ├── SKKUserDictionaryFile.swift ├── ThemeColor.swift ├── Utilities.swift └── ja.lproj │ └── Localizable.strings ├── FlickSKK.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ ├── FlickSKK.xcscheme │ ├── FlickSKKKeyboard.xcscheme │ └── Memo.xcscheme ├── FlickSKK.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── FlickSKK ├── AdditionalDictionaries.swift ├── AdditionalDictionaryViewController.swift ├── AppDelegate.swift ├── DictionaryInfo.swift ├── DownloadDictionary.swift ├── DownloadDictionaryViewController.swift ├── FlickSKK.entitlements ├── HeadUpProgressViewController.swift ├── Images.xcassets │ ├── AppIcon.appiconset │ │ ├── AppIcon-1024.png │ │ ├── Contents.json │ │ ├── Icon-App-20x20@1x.png │ │ ├── Icon-App-20x20@2x.png │ │ ├── Icon-App-20x20@3x.png │ │ ├── Icon-App-29x29@1x.png │ │ ├── Icon-App-29x29@2x.png │ │ ├── Icon-App-29x29@3x.png │ │ ├── Icon-App-40x40@1x.png │ │ ├── Icon-App-40x40@2x.png │ │ ├── Icon-App-40x40@3x.png │ │ ├── Icon-App-57x57@1x.png │ │ ├── Icon-App-57x57@2x.png │ │ ├── Icon-App-60x60@2x.png │ │ ├── Icon-App-60x60@3x.png │ │ ├── Icon-App-76x76@1x.png │ │ ├── Icon-App-76x76@2x.png │ │ └── Icon-App-83.5x83.5@2x.png │ └── Contents.json ├── Info.plist ├── LaunchScreen.xib ├── MainMenuViewController.swift ├── Settings.bundle │ ├── Acknowledgements.plist │ ├── Root.plist │ ├── en.lproj │ │ └── Root.strings │ └── ja.lproj │ │ └── Root.strings ├── SortDictionary.swift ├── Tempfile.swift ├── UserDictionaryViewController.swift ├── ValidateDictionary.swift ├── WebViewController.swift ├── WordRegisterViewController.swift ├── flickskk_white.png ├── html │ ├── HowToUse.html │ ├── License.html │ ├── Setup.html │ ├── common.css │ ├── globe.png │ └── how_to_use │ │ ├── 1-Preferences.png │ │ ├── 2-General.png │ │ ├── 3-Keyboard.png │ │ ├── 4-Keyboard.png │ │ ├── 5-Add.png │ │ ├── 6-FlickSKK.png │ │ ├── 7-KeyboardList.png │ │ ├── 8-FullAccess.png │ │ └── 9-Change.png ├── iTunesArtwork └── iTunesArtwork@2x ├── FlickSKKKeyboard ├── Candidate.swift ├── ComposeMode.swift ├── ComposeModeFactory.swift ├── ComposeModePresenter.swift ├── DictionaryEngine.swift ├── FlickSKKKeyboard.entitlements ├── Info.plist ├── KanaFlickKey.swift ├── KeyButton.swift ├── KeyButtonFlickPopup.swift ├── KeyHandler.swift ├── KeyPad.swift ├── KeyRepeatTimer.swift ├── KeyboardImages.xcassets │ ├── flickskk-arrow.imageset │ │ ├── Contents.json │ │ └── flickskk-arrow.pdf │ └── globe.imageset │ │ ├── Contents.json │ │ └── world.pdf ├── KeyboardMode.swift ├── KeyboardViewController.swift ├── SKKDelegate.swift ├── SKKEngine.swift ├── SKKInputMode.swift ├── SKKKeyEvent.swift ├── SessionView.swift ├── TextEngine.swift └── skk.jisyo ├── FlickSKKTests ├── BinarySearchSpec.swift ├── ComposeModePresenterSpec.swift ├── DictionaryEngineSpec.swift ├── EntryParserSpec.swift ├── Info.plist ├── KeyHandlerBaseSpec.swift ├── KeyHandlerDirectInputSpec.swift ├── KeyHandlerKanaComposeSpec.swift ├── KeyHandlerKanjiComposeSpec.swift ├── KeyHandlerWordRegisterWithDirectInputSpec.swift ├── KeyHandlerWordRegisterWithKanaComposeSpec.swift ├── LoadLocalDictionarySpec.swift ├── MockDelegate.swift ├── NumberFliterSpec.swift ├── NumberFormatterSpec.swift ├── SKKDictionaryLocalFileSpec.swift ├── SKKDictionarySpec.swift ├── SKKDictionaryUserFileSpec.swift ├── SKKEngineSpec.swift ├── TextEngineSpec.swift └── UtilitiesSpec.swift ├── Gemfile ├── Gemfile.lock ├── Memo ├── AppDelegate.swift ├── Base.lproj │ └── LaunchScreen.xib ├── Images.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Info.plist └── ViewController.swift ├── Podfile ├── Podfile.lock ├── README.mkdn ├── ci_scripts └── ci_post_clone.sh ├── fastlane ├── Deliverfile ├── Fastfile ├── Gymfile ├── README.md ├── Scanfile ├── metadata │ ├── app_icon.png │ ├── copyright.txt │ ├── ja │ │ ├── apple_tv_privacy_policy.txt │ │ ├── description.txt │ │ ├── keywords.txt │ │ ├── marketing_url.txt │ │ ├── name.txt │ │ ├── privacy_url.txt │ │ ├── promotional_text.txt │ │ ├── release_notes.txt │ │ ├── subtitle.txt │ │ └── support_url.txt │ ├── primary_category.txt │ ├── primary_first_sub_category.txt │ ├── primary_second_sub_category.txt │ ├── secondary_category.txt │ ├── secondary_first_sub_category.txt │ └── secondary_second_sub_category.txt └── screenshots │ └── ja │ ├── 1_iPad Pro (12.9-inch) (3rd generation).png │ ├── 1_iPhone Xs Max.png │ ├── 1_ipadPro_1.Simulator Screen Shot Aug 30, 2016, 23.21.39.png │ ├── 1_ipad_1.iPad-1.png │ ├── 1_iphone35_1.iPhone-3.5inch-1.png │ ├── 1_iphone4_1.iPhone-4.0inch-1.png │ ├── 1_iphone6Plus_1.iPhone-5.5inch-1.png │ ├── 1_iphone6_1.iPhone-4.7inch-1.png │ ├── 2_iPad Pro (12.9-inch) (3rd generation).png │ ├── 2_iPhone Xs Max.png │ ├── 2_ipadPro_2.Simulator Screen Shot Aug 30, 2016, 23.21.57.png │ ├── 2_ipad_2.iPad-2.png │ ├── 2_iphone35_2.iPhone-3.5inch-2.png │ ├── 2_iphone4_2.iPhone-4.0inch-2.png │ ├── 2_iphone6Plus_2.iPhone-5.5inch-2.png │ ├── 2_iphone6_2.iPhone-4.7inch-2.png │ ├── 3_iPad Pro (12.9-inch) (3rd generation).png │ ├── 3_iPhone Xs Max.png │ ├── 3_ipadPro_3.Simulator Screen Shot Aug 30, 2016, 23.22.18.png │ ├── 3_ipad_3.iPad-3.png │ ├── 3_iphone35_3.iPhone-3.5inch-3.png │ ├── 3_iphone4_3.iPhone-4.0inch-3.png │ ├── 3_iphone6Plus_3.iPhone-5.5inch-3.png │ ├── 3_iphone6_3.iPhone-4.7inch-3.png │ ├── 4_iPad Pro (12.9-inch) (3rd generation).png │ ├── 4_iPhone Xs Max.png │ ├── 4_ipadPro_4.Simulator Screen Shot Aug 30, 2016, 23.27.24.png │ ├── 4_ipad_4.iPad-4.png │ ├── 4_iphone35_4.iPhone-3.5inch-4.png │ ├── 4_iphone4_4.iPhone-4.0inch-4.png │ ├── 4_iphone6Plus_4.iPhone-5.5inch-4.png │ └── 4_iphone6_4.iPhone-4.7inch-4.png └── misc ├── screenshot.rb ├── sort.rb └── strip.rb /.gitignore: -------------------------------------------------------------------------------- 1 | # Mac 2 | .DS_Store 3 | .cocoapods_appgroup 4 | 5 | # Xcode 6 | /build/* 7 | *.pbxuser 8 | !default.pbxuser 9 | *.mode1v3 10 | !default.mode1v3 11 | *.mode2v3 12 | !default.mode2v3 13 | *.perspectivev3 14 | !default.perspectivev3 15 | xcuserdata 16 | profile 17 | *.moved-aside 18 | *.xccheckout 19 | 20 | #CocoaPod 21 | /Pods/* 22 | 23 | #other 24 | /tmp/ 25 | 26 | /Config.xcconfig 27 | 28 | fastlane/report.xml 29 | fastlane/Preview.html 30 | fastlane/test_output/ 31 | /*.dSYM.zip 32 | /*.ipa 33 | -------------------------------------------------------------------------------- /Common/Appearance.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Appearance.swift 3 | // FlickSKK 4 | // 5 | // Created by MIZUNO Hiroki on 2/15/15. 6 | // Copyright (c) 2015 BAN Jun. All rights reserved. 7 | // 8 | import UIKit 9 | 10 | class Appearance { 11 | class func normalFont(_ size : CGFloat) -> UIFont { 12 | return UIFont(name: "HiraKakuProN-W3", size: size)! 13 | } 14 | class func boldFont(_ size : CGFloat) -> UIFont { 15 | return UIFont(name: "HiraKakuProN-W6", size: size)! 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Common/AsyncLoader.swift: -------------------------------------------------------------------------------- 1 | // 非同期に辞書のロードを行なう 2 | class AsyncLoader { 3 | var initialized = false 4 | 5 | // 辞書をロードする 6 | func load(_ closure: @escaping () -> ()) { 7 | async { 8 | closure() 9 | self.initialized = true 10 | } 11 | } 12 | 13 | // ロード完了をまつ 14 | func wait() { 15 | while !self.initialized { 16 | RunLoop.current.run(until: Date(timeIntervalSinceNow: 0.1)) 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Common/BinarySearch.swift: -------------------------------------------------------------------------------- 1 | // SKK辞書から該当エントリを二分探索する。 2 | // 3 | // SKK辞書は以下のようなエントリの列となっている。(引用元: 沖縄語辞書) 4 | // 5 | // あたえ /安多栄;‖姓/ 6 | // あたり /中;@@@‖姓/當;myama_kj@pref98‖姓/ 7 | // あだ /安田;‖各種/ 8 | // あだがーしま /安田ヶ島;国頭村 あだが?‖接尾語付き地名/ 9 | // 10 | // ここから目的のエントリを見付けるため、スペースの前の部分に関して二分探索を行なう必要がある。 11 | // 12 | // 既存の二分探索ライブラリでは「スペースの前」という部分に対応していなかったため、自分で実装する。 13 | // 14 | // また送りありエントリは辞書の逆順で格納されているので、比較順を逆にできるオプションも実装する。 15 | class BinarySearch { 16 | fileprivate let entries : NSArray 17 | fileprivate let compare : ComparisonResult 18 | 19 | init(entries : NSArray, reverse : Bool) { 20 | self.entries = entries 21 | self.compare = reverse ? 22 | ComparisonResult.orderedDescending : 23 | ComparisonResult.orderedAscending 24 | 25 | } 26 | 27 | func call(_ target : NSString) -> String? { 28 | return binarySearch(target, begin: 0, end: entries.count) 29 | } 30 | 31 | fileprivate func binarySearch(_ target : NSString, begin : Int, end : Int) -> String? { 32 | if begin == end { return .none } 33 | if begin + 1 == end { 34 | let x = entries[begin] as! NSString 35 | if x.hasPrefix(target as String) { 36 | return x as String 37 | } else { 38 | return .none 39 | } 40 | } 41 | 42 | let mid = (end - begin) / 2 + begin; 43 | let x = entries[mid] as! NSString 44 | if x.hasPrefix(target as String) { 45 | return x as String 46 | } else { 47 | if target.compare(x as String, options: .literal) == compare { 48 | return binarySearch(target, begin: begin, end: mid) 49 | } else { 50 | return binarySearch(target, begin: mid, end: end) 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Common/DictionaryCache.swift: -------------------------------------------------------------------------------- 1 | private var kDicitonary : SKKLocalDictionaryFile? 2 | private var kCache : [URL:(Date, Any)] = [:] 3 | private let writeQueue = DispatchQueue(label: "DictionaryCache.write") 4 | 5 | // 辞書のロードには時間がかかるので、一度ロードした結果をキャッシュする 6 | // グローバル変数にいれておけば、次回起動時にも残っている(ことがある) 7 | class DictionaryCache { 8 | // L辞書等のインストール済みの辞書をロードする 9 | // FIXME: 現時点では二個以上の辞書ファイルは存在しないと仮定している 10 | func loadLocalDicitonary(_ url: URL, closure: (URL) -> SKKLocalDictionaryFile) -> SKKLocalDictionaryFile { 11 | if kDicitonary == nil { 12 | kDicitonary = closure(url) 13 | } 14 | return kDicitonary! 15 | } 16 | 17 | // ユーザごとに作られる辞書をロードする(例: 単語登録結果、学習結果) 18 | func loadUserDicitonary(_ url: URL, closure: (URL) -> T) -> T { 19 | if let mtime = getModifiedTime(url) { 20 | if let (Exact, file) = kCache[url] { 21 | if Exact == mtime { 22 | // キャッシュが有効 23 | return file as! T 24 | } else { 25 | // キャシュが無効になっている 26 | let newFile = closure(url) 27 | writeQueue.sync { 28 | kCache[url] = (mtime, newFile) 29 | } 30 | return newFile 31 | } 32 | } else { 33 | // キャッシュが存在しない 34 | let file = closure(url) 35 | writeQueue.sync { 36 | kCache[url] = (mtime, file) 37 | } 38 | return file 39 | } 40 | } else { 41 | // ロード対象のファイルが存在しない 42 | return closure(url) 43 | } 44 | } 45 | 46 | // キャッシュの最終更新日時を更新する 47 | func update(_ url: URL, closure : () -> ()) { 48 | closure() 49 | if let (_, file) = kCache[url] { 50 | if let mtime = getModifiedTime(url) { 51 | writeQueue.sync { 52 | kCache[url] = (mtime, file) 53 | } 54 | } 55 | } 56 | } 57 | 58 | fileprivate func getModifiedTime(_ url: URL) -> Date? { 59 | let fm = FileManager.default 60 | let path = url.path 61 | guard let attrs = try? fm.attributesOfItem(atPath: path) else { return nil } 62 | return attrs[FileAttributeKey.modificationDate] as? Date 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Common/DictionarySettings.swift: -------------------------------------------------------------------------------- 1 | import AppGroup 2 | 3 | // 辞書のパスを管理する 4 | class DictionarySettings { 5 | // テスト時は違うBundleからロードする 6 | // FIXME: もっといい感じに書きたい 7 | fileprivate struct ClassProperty { 8 | static var bundle : Bundle? 9 | } 10 | class var bundle: Bundle? { 11 | get { 12 | return ClassProperty.bundle 13 | } 14 | set { 15 | ClassProperty.bundle = newValue 16 | } 17 | } 18 | 19 | // ユーザ辞書(単語登録) 20 | class func defaultUserDictionaryURL() -> URL { 21 | return AppGroup.url(forResource: "Library/skk.jisyo") ?? home("Library/skk.jisyo") 22 | } 23 | 24 | // 変換結果の学習 25 | class func defaultLearnDictionaryURL() -> URL { 26 | return AppGroup.url(forResource: "Library/skk.learn.jisyo") ?? home("Library/skk.learn.jisyo") 27 | } 28 | 29 | // q確定の結果の学習 30 | class func defaultPartialDictionaryURL() -> URL { 31 | return AppGroup.url(forResource: "Library/skk.partial.jisyo") ?? home("Library/skk.partial.jisyo") 32 | } 33 | 34 | // 追加辞書 35 | class func additionalDictionaryURL() -> URL { 36 | return AppGroup.url(forResource: "Library/additional") ?? home("Library/additional") 37 | } 38 | 39 | // 組込みの辞書(L辞書とか) 40 | class func defaultDicitonaryURL() -> URL { 41 | // 辞書は必ず組込まれているはず 42 | return (DictionarySettings.bundle ?? Bundle.main).url(forResource: "skk", withExtension: "jisyo")! 43 | } 44 | 45 | class func home(_ path : String) -> URL { 46 | return URL(fileURLWithPath: NSHomeDirectory(), isDirectory: true).appendingPathComponent(path) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Common/EntryParser.swift: -------------------------------------------------------------------------------- 1 | // SKK辞書の各エントリをパースする。 2 | // 3 | // SKKの各エントリは以下のようになっている。 4 | // 5 | // わb /詫;謝罪する/侘;侘び寂び/ 6 | // 7 | // ここから、「詫」「侘」を取り出す。 8 | // 9 | // この際、以下の処理を行なう。 10 | // 11 | // - 現在、サポートされていないため、アノテーションを除去する 12 | // - "/" といった登録できない文字のエスケープ解除 13 | class EntryParser { 14 | fileprivate let entry : String 15 | 16 | init(entry : String) { 17 | self.entry = entry 18 | } 19 | 20 | func title() -> String? { 21 | if let n = self.entry.firstIndex(of: " ") { 22 | return String(self.entry[.. [String] { 30 | return rawWords().map { 31 | self.unescape(self.stripAnnotation($0)) 32 | } 33 | } 34 | 35 | // 単語を追加 36 | func append(_ word : String) -> String { 37 | let xs = words().filter({ x in 38 | x != word 39 | }) 40 | return join([ word ] + xs) 41 | } 42 | 43 | // 単語の削除 44 | func remove(_ word : String) -> String? { 45 | let xs = words().filter({ x in 46 | x != word 47 | }) 48 | 49 | if xs.isEmpty { 50 | return nil 51 | } else { 52 | return join(xs) 53 | } 54 | } 55 | 56 | // 単語リストを結合して、SKKのエントリにする 57 | // ["a", "b"] => /a/b/ 58 | fileprivate func join(_ xs : [String]) -> String { 59 | return xs.reduce("", {(x,y) in x + "/" + self.escape(y) }) + "/" 60 | } 61 | 62 | // 特殊な文字は置換する。 63 | // 置換方式はSKKによって違うが、ここではAquaSKKと同様に[xx]に置換する方式を取る。 64 | fileprivate let EscapeStrings = [("[","[5b]"), ("/", "[2f]"), (";","[3b]")] 65 | 66 | fileprivate func escape(_ str : String) -> String { 67 | return EscapeStrings.reduce(str) { (str, x) in 68 | let (from, to) = x 69 | return str.replacingOccurrences(of: from, with: to, options: [], range: nil) 70 | } 71 | } 72 | 73 | // 単語ごとに分割する 74 | func rawWords() -> [String] { 75 | let xs = self.entry.components(separatedBy: "/") 76 | if xs.count <= 2 { 77 | return [] 78 | } else { 79 | return Array(xs[1...xs.count-2]) 80 | } 81 | } 82 | 83 | // エスケープの解除 84 | fileprivate func unescape(_ str : String) -> String { 85 | return EscapeStrings.reduce(str) { (str, x) in 86 | let (from, to) = x 87 | return str.replacingOccurrences(of: to, with: from, options: [], range: nil) 88 | } 89 | } 90 | 91 | // アノテーションの除去 92 | fileprivate func stripAnnotation(_ str : String) -> String { 93 | if let index = str.firstIndex(of: ";") { 94 | return String(str[.. 13 | 14 | @interface IOUtil : NSObject 15 | + (void)each: (NSString*)path with:(void (^)(NSString *str))block; 16 | @end 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /Common/IOUtil.mm: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // IOUtil.m 4 | // FlickSKK 5 | // 6 | // Created by mzp on 2014/10/01. 7 | // Copyright (c) 2014年 BAN Jun. All rights reserved. 8 | // 9 | 10 | #import 11 | #import "IOUtil.h" 12 | #include 13 | 14 | @implementation IOUtil 15 | 16 | + (void)each: (NSString*)path with:(void (^)(NSString *str))block { 17 | std::ifstream ifs([path cStringUsingEncoding:[NSString defaultCStringEncoding]]); 18 | std::string line; 19 | while (std::getline(ifs, line)) 20 | { 21 | NSString *string = [NSString stringWithCString:line.c_str() encoding: NSUTF8StringEncoding]; 22 | block(string); 23 | } 24 | ifs.close(); 25 | } 26 | 27 | @end -------------------------------------------------------------------------------- /Common/IdFilter.swift: -------------------------------------------------------------------------------- 1 | // 何もしないフィルタ。 2 | // 3 | // 他のフィルタで変換されてしまうような要素をそのまま検索するためのフィルタ。 4 | // 例えばNumberFilterでは以下のようなエントリを変換できない。 5 | // 6 | // 16にち /16日/ 7 | // 8 | // 上記例は特殊だが、zipcode辞書等では発生しうる。 9 | class IdFilter : SKKFilter { 10 | func call(_ target: String, binarySearch: BinarySearch, parse: (String) -> [String]) -> [String] { 11 | if let line = binarySearch.call(target as NSString) { 12 | return parse(line) 13 | } else { 14 | return [] 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Common/LoadLocalDictionary.swift: -------------------------------------------------------------------------------- 1 | // SKKの辞書ファイルをロードする。 2 | // 3 | // SKK辞書は以下の形式でテキストファイルに格納されている。 4 | // 5 | // ;; -*- mode: fundamental; coding: euc-jp -*- 6 | // ;; Large size dictionary for SKK system 7 | // ;; Copyright (C) 1988-1995, 1997, 1999-2014 8 | // (snip) 9 | // ;; okuri-ari entries. 10 | // をs /惜/ 11 | // ゐr /居/ 12 | // われらg /我等/ 13 | // われしr /我知/ 14 | // (snip) 15 | // ;; okuri-nasi entries. 16 | // ! /!/感嘆符/ 17 | // !! /!!/ 18 | // != /≠/ 19 | // " /″;second/“;doublequote(open)/”;doublequote(close)/〃;繰り返し記号/ 20 | // # /#1/#3/#2/#;number/#0/#8/#4/#5/ 21 | // 22 | // こういったテキストファイルを読み込み、送りありのエントリと、送りなしのエントリを配列として返す。 23 | // 24 | // おもに速度向上・メモリ節約を目的として 25 | // 26 | // - まとめて読むのではなく、一行づつ読み込む。(ちゃんとベンチマークしてない) 27 | // - ソート順の変更や、文字コードの変更は行なわず、必要になるまで遅延する。 28 | // - 行の内容のパースも、必要になるまで遅延する。 29 | // - Swiftの配列はシミュレータ環境では遅いため、NSArray/NSMutableArrayを利用する 30 | // 31 | // といったことを行なっている。 32 | class LoadLocalDictionary { 33 | fileprivate var ari : NSMutableArray = NSMutableArray() 34 | fileprivate var nasi : NSMutableArray = NSMutableArray() 35 | 36 | init(url : URL) { 37 | let path = url.path 38 | 39 | var isOkuriAri = true 40 | IOUtil.each(path, with: { line -> Void in 41 | guard let s = line else { return } 42 | // toggle 43 | if s.hasPrefix(";; okuri-nasi entries.") { 44 | isOkuriAri = false 45 | } 46 | // skip comment 47 | if s.hasPrefix(";") { return } 48 | 49 | // skip empty line 50 | if s == "" { return } 51 | 52 | if isOkuriAri { 53 | self.ari.add(s) 54 | } else { 55 | self.nasi.add(s) 56 | } 57 | }) 58 | } 59 | 60 | func okuriAri() -> NSArray { 61 | return ari 62 | } 63 | 64 | func okuriNasi() -> NSArray { 65 | return nasi 66 | } 67 | 68 | func count() -> Int { 69 | return ari.count + nasi.count 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Common/LocalFile.swift: -------------------------------------------------------------------------------- 1 | // ローカルにUTF8のテキストファイルに書き出すためのクラス。 2 | // NSFileManagerをラップして、ファイルが存在しているかどうかを気にせずに使えるようにした。 3 | // 4 | // 「ローカル」という命名はiCloud対応を意識してのことだが、現時点で特にそのような仕組みは実装していない。 5 | class LocalFile { 6 | fileprivate let handle : FileHandle? 7 | 8 | init?(url : URL) { 9 | let path = url.path 10 | 11 | if !FileManager.default.fileExists(atPath: path) { 12 | FileManager.default.createFile(atPath: path, contents: nil, attributes:nil) 13 | } 14 | 15 | if let handle = FileHandle(forWritingAtPath: path) { 16 | self.handle = handle 17 | // ファイルを開くと内容を空にするようにする。 18 | clear() 19 | } else { 20 | self.handle = nil 21 | return nil 22 | } 23 | } 24 | 25 | func writeln(_ str : String) { 26 | write(str + "\n") 27 | } 28 | 29 | func write(_ str : String) { 30 | let s = str as NSString 31 | let data = Data(bytes: UnsafeRawPointer(s.utf8String!), 32 | count: s.lengthOfBytes(using: String.Encoding.utf8.rawValue)) 33 | handle?.write(data) 34 | } 35 | 36 | func close() { 37 | self.handle?.closeFile() 38 | } 39 | 40 | func clear() { 41 | self.handle?.truncateFile(atOffset: 0) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Common/NumberFilter.swift: -------------------------------------------------------------------------------- 1 | // 数値変換フィルタ 2 | // 3 | // 検索語が「16や」だった場合、以下のようなエントリをヒットするようにする。 4 | // 5 | // #や /#1夜/#2夜/ 6 | // 7 | // #1 等はそれぞれ「全角数字に変換する」などのルールが決まっている。 8 | class NumberFilter : SKKFilter { 9 | // 見出し語中に含まれる数字を置換するための正規表現(e.g. 10, 231) 10 | fileprivate let regexp : NSRegularExpression! = try? NSRegularExpression(pattern: "[0-9]+", options: []) 11 | 12 | // 単語中の #1 等を置換するための正規表現(e.g. #1, #2) 13 | fileprivate let template : NSRegularExpression! = try? NSRegularExpression(pattern: "#[0-9]", options: []) 14 | 15 | func call(_ target: String, binarySearch: BinarySearch, parse: (String) -> [String]) -> [String] { 16 | // 検索語の置換のために、数字を記憶する 17 | let numbers = self.numbers(target) 18 | 19 | if numbers.isEmpty { return [] } 20 | 21 | // 検索する 22 | if let line = binarySearch.call(self.hashnize(target) as NSString) { 23 | let entries = parse(line) 24 | 25 | // 検索結果を置換する 26 | return entries.map { 27 | self.stringnize($0, numbers: numbers) 28 | } 29 | } else { 30 | return [] 31 | } 32 | } 33 | 34 | // 見出し語中の数値を抜き出す 35 | fileprivate func numbers(_ value : String) -> [Int64] { 36 | let xs = regexp.matches(in: value, 37 | options: [], 38 | range: NSMakeRange(0, value.utf16.count)) 39 | return xs.map({ x in 40 | let n : NSString = (value as NSString).substring(with: x.range) as NSString 41 | return n.longLongValue 42 | }) 43 | } 44 | 45 | // 検索用に変換する: 16や -> #や 46 | fileprivate func hashnize(_ target : String) -> String { 47 | return regexp.stringByReplacingMatches(in: target, 48 | options: [], 49 | range: NSMakeRange(0, target.utf16.count), 50 | withTemplate: "#") 51 | } 52 | 53 | // 単語中の #1 等を置換する 54 | fileprivate func stringnize(_ entry : String, numbers : [Int64]) -> String { 55 | let result : NSMutableString = 56 | entry.mutableCopy() as! NSMutableString 57 | 58 | var ret = 59 | template.firstMatch(in: result as String, options: [], range: NSMakeRange(0, result.length)) 60 | 61 | var index = 0 62 | 63 | while let x = ret { 64 | let matched = result.substring(with: x.range) 65 | 66 | template.replaceMatches(in: result, 67 | options: [], 68 | range: x.range, 69 | withTemplate: stringFor(numbers[index], entry: matched)) 70 | index += 1 71 | ret = template.firstMatch(in: result as String, options: [], range: NSMakeRange(0, result.length)) 72 | } 73 | 74 | return result as String 75 | } 76 | 77 | // #1 等に応じて、対応した文字に変換する 78 | fileprivate func stringFor(_ n : Int64, entry : String) -> String { 79 | let formatter = NumberFormatter(value: n) 80 | switch entry { 81 | case "#0": 82 | return formatter.asAscii() 83 | case "#1": 84 | return formatter.asFullWidth() 85 | case "#2": 86 | return formatter.asJapanese() 87 | case "#3": 88 | return formatter.asKanji() 89 | default: 90 | return entry 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /Common/NumberFormatter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NumberFormatter.swift 3 | // FlickSKK 4 | // 5 | // Created by MIZUNO Hiroki on 12/27/14. 6 | // Copyright (c) 2014 BAN Jun. All rights reserved. 7 | // 8 | 9 | class NumberFormatter { 10 | let value : Int64 11 | init(value : Int64) { 12 | self.value = value 13 | } 14 | 15 | func asAscii() -> String { 16 | return NSString(format: "%d", self.value) as String 17 | } 18 | 19 | func asFullWidth() -> String { 20 | return conv(asAscii(), from: "0123456789", to: "0123456789") 21 | } 22 | 23 | func asJapanese() -> String { 24 | return conv(asAscii(), from: "0123456789", to: "〇一二三四五六七八九") 25 | } 26 | 27 | func asKanji() -> String { 28 | if value == 0 { 29 | return "零" 30 | } else { 31 | return toKanji(value) 32 | } 33 | } 34 | 35 | fileprivate func conv(_ target : String, from : String, to : String) -> String { 36 | return implode(Array(target).map( { c in 37 | tr(c, from: from, to: to) ?? c 38 | })) 39 | } 40 | 41 | 42 | fileprivate func toKanji_lt_10(_ n : Int) -> String? { 43 | // where n < 10 44 | if n == 0 { 45 | return .none 46 | } else if n == 1 { 47 | return "" 48 | } else { 49 | let xs = Array("__二三四五六七八九") 50 | return String(xs[n]) 51 | } 52 | } 53 | 54 | fileprivate func toKanjiDigit(_ n : Int64, at : Int64, name: String) -> String { 55 | let m : Int = Int((n / at) % 10) 56 | return toKanji_lt_10(m).map({ c in c + name }) ?? "" 57 | } 58 | 59 | fileprivate func toKanji_lt_10000(_ n : Int64) -> String? { // where n < 10_000 60 | if n == 0 { 61 | return .none 62 | } else if n == 1 { 63 | return "一" 64 | } else { 65 | let _1000 = toKanjiDigit(n, at: 1000, name: "千") 66 | let _100 = toKanjiDigit(n, at: 100, name: "百") 67 | let _10 = toKanjiDigit(n, at: 10, name: "十") 68 | let _1 = toKanjiDigit(n, at: 1, name: "") 69 | return _1000 + _100 + _10 + _1 70 | } 71 | } 72 | 73 | fileprivate func toKanjiDigits(_ n : Int64, at : Int64, name: String) -> String { 74 | return toKanji_lt_10000((n / at) % Int64(1_0000)).map({ c in c + name }) ?? "" 75 | } 76 | 77 | fileprivate func toKanji(_ n : Int64) -> String { 78 | let 兆 = toKanjiDigits(n, at: 1_0000_0000_0000, name: "兆") 79 | let 億 = toKanjiDigits(n, at: 1_0000_0000, name: "億") 80 | let 万 = toKanjiDigits(n, at: 1_0000, name: "万") 81 | let rest = toKanjiDigits(n, at: 1, name: "") 82 | 83 | return 兆 + 億 + 万 + rest 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Common/SKKDictionary.swift: -------------------------------------------------------------------------------- 1 | // FlickSKKで使う辞書のインタフェース 2 | // メモリを節約するために 3 | // ・辞書のロードの非同期実行 4 | // ・ロード結果のキャッシュ 5 | // などを行なう。 6 | class SKKDictionary : NSObject { 7 | // すべての辞書(先頭から順に検索される) 8 | fileprivate var dictionaries : [ SKKDictionaryFile ] = [] 9 | 10 | // ダイナミック変換用辞書 11 | fileprivate var dynamicDictionaries : [ SKKUserDictionaryFile ] = [] 12 | 13 | // ユーザ辞書 14 | fileprivate var userDictionary : SKKUserDictionaryFile? 15 | 16 | // 学習辞書 17 | fileprivate var learnDictionary : SKKUserDictionaryFile? 18 | 19 | // 略語辞書 20 | fileprivate var partialDictionary : SKKUserDictionaryFile? 21 | 22 | // ロード完了を監視するために Key value observing を使う 23 | @objc dynamic var isWaitingForLoad : Bool = false 24 | class func isWaitingForLoadKVOKey() -> String { return "isWaitingForLoad" } 25 | 26 | fileprivate let loader = AsyncLoader() 27 | fileprivate let cache = DictionaryCache() 28 | 29 | class func resetLearnDictionary() { 30 | for url in [DictionarySettings.defaultLearnDictionaryURL(), DictionarySettings.defaultPartialDictionaryURL()] { 31 | _ = try? FileManager.default.removeItem(at: url as URL) 32 | } 33 | } 34 | 35 | class func additionalDictionaries() -> [URL] { 36 | do { 37 | let manager = FileManager.default 38 | let url = DictionarySettings.additionalDictionaryURL() 39 | return try manager.contentsOfDirectory(at: url as URL, 40 | includingPropertiesForKeys: nil, 41 | options: []) 42 | } catch { 43 | return [] 44 | } 45 | } 46 | 47 | override init() { 48 | super.init() 49 | loader.load { 50 | let dictionary = self.cache.loadLocalDicitonary(DictionarySettings.defaultDicitonaryURL()) { 51 | SKKLocalDictionaryFile(url: $0) 52 | } 53 | self.userDictionary = self.cache.loadUserDicitonary(DictionarySettings.defaultUserDictionaryURL()) { 54 | SKKUserDictionaryFile(url: $0) 55 | } 56 | self.learnDictionary = self.cache.loadUserDicitonary(DictionarySettings.defaultLearnDictionaryURL()) { 57 | SKKUserDictionaryFile(url: $0) 58 | } 59 | 60 | self.partialDictionary = self.cache.loadUserDicitonary(DictionarySettings.defaultPartialDictionaryURL()){ 61 | SKKUserDictionaryFile(url: $0) 62 | } 63 | 64 | let xs : [SKKDictionaryFile] = SKKDictionary.additionalDictionaries().map { url in 65 | self.cache.loadUserDicitonary(url) { 66 | SKKLocalDictionaryFile(url: $0) 67 | } 68 | } 69 | 70 | self.dictionaries = [ self.learnDictionary!, self.userDictionary!, dictionary ] + xs 71 | self.dynamicDictionaries = [ self.partialDictionary!, self.learnDictionary!, self.userDictionary! ] 72 | } 73 | } 74 | 75 | // 辞書を検索する 76 | func find(_ normal : String, okuri : String?) -> [ String ] { 77 | self.waitForLoading() 78 | 79 | let xs : [String] = self.dictionaries.flatMap { 80 | $0.find(normal, okuri: okuri) 81 | }.unique() 82 | 83 | return xs 84 | } 85 | 86 | // ダイナミック変換用の辞書検索 87 | func findDynamic(_ prefix : String) -> [(kana: String, kanji: String)] { 88 | self.waitForLoading() 89 | 90 | let xs : [(kana : String, kanji: String)] = self.dynamicDictionaries.flatMap { 91 | $0.findWith(prefix) 92 | }.uniqueBy { c in c.kanji } 93 | 94 | return xs 95 | } 96 | 97 | // 単語を登録する 98 | func register(_ normal : String, okuri: String?, kanji: String) { 99 | userDictionary?.register(normal, okuri: okuri, kanji: kanji) 100 | async { 101 | self.cache.update(DictionarySettings.defaultUserDictionaryURL()) { 102 | self.userDictionary?.serialize() 103 | } 104 | } 105 | } 106 | 107 | // 確定結果を学習する 108 | func learn(_ normal : String, okuri: String?, kanji: String) { 109 | learnDictionary?.register(normal, okuri: okuri, kanji: kanji) 110 | async { 111 | self.cache.update(DictionarySettings.defaultLearnDictionaryURL()) { 112 | self.learnDictionary?.serialize() 113 | } 114 | } 115 | } 116 | 117 | // InputModeChangeによる確定を学習する 118 | func partial(_ kana: String, okuri: String?, kanji: String) { 119 | partialDictionary?.register(kana, okuri: okuri, kanji: kanji) 120 | async { 121 | self.cache.update(DictionarySettings.defaultPartialDictionaryURL()) { 122 | self.partialDictionary?.serialize() 123 | } 124 | } 125 | } 126 | 127 | // 辞書のロード完了を待つ 128 | func waitForLoading() { 129 | if loader.initialized { return } 130 | 131 | self.isWaitingForLoad = true 132 | self.loader.wait() 133 | self.isWaitingForLoad = false 134 | } 135 | 136 | // ユーザ辞書を取得する(設定アプリ用) 137 | class func defaultUserDictionary() -> SKKUserDictionaryFile { 138 | let url = DictionarySettings.defaultUserDictionaryURL() 139 | return SKKUserDictionaryFile(url: url) 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /Common/SKKDictionaryEntry.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SKKDictionaryEntry.swift 3 | // 4 | // 5 | // Created by MIZUNO Hiroki on 11/4/14. 6 | // 7 | // 8 | 9 | import Foundation 10 | // FIXME: comparison operators with optionals were removed from the Swift Standard Libary. 11 | // Consider refactoring the code to use the non-optional operators. 12 | fileprivate func < (lhs: T?, rhs: T?) -> Bool { 13 | switch (lhs, rhs) { 14 | case let (l?, r?): 15 | return l < r 16 | case (nil, _?): 17 | return true 18 | default: 19 | return false 20 | } 21 | } 22 | 23 | 24 | enum SKKDictionaryEntry : Comparable { 25 | case skkDictionaryEntry(kanji : String, kana : String, okuri : String?) 26 | } 27 | 28 | func ==(l: SKKDictionaryEntry, r: SKKDictionaryEntry) -> Bool { 29 | switch (l,r) { 30 | case (.skkDictionaryEntry(kanji: let kanji1, kana: let kana1, okuri: let okuri1), 31 | .skkDictionaryEntry(kanji: let kanji2, kana: let kana2, okuri: let okuri2)): 32 | return kanji1 == kanji2 && kana1 == kana2 && okuri1 == okuri2 33 | } 34 | } 35 | 36 | func <(l: SKKDictionaryEntry, r: SKKDictionaryEntry) -> Bool { 37 | switch (l,r) { 38 | case (.skkDictionaryEntry(kanji: let kanji1, kana: let kana1, okuri: let okuri1), 39 | .skkDictionaryEntry(kanji: let kanji2, kana: let kana2, okuri: let okuri2)): 40 | return kana1 < kana2 || okuri1 < okuri2 || kanji1 < kanji2 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Common/SKKDictionaryFile.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SKKDictionaryFile.swift 3 | // FlickSKK 4 | // 5 | // Created by mzp on 2014/10/17. 6 | // Copyright (c) 2014年 BAN Jun. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol SKKDictionaryFile { 12 | func find(_ normal : String, okuri : String?) -> [ String ] 13 | } 14 | -------------------------------------------------------------------------------- /Common/SKKFilter.swift: -------------------------------------------------------------------------------- 1 | // 数値変換等の特殊な変換を実現するためのフィルタ。 2 | protocol SKKFilter { 3 | func call(_ target : String, binarySearch: BinarySearch, parse : (String) -> [String]) -> [String] 4 | } 5 | -------------------------------------------------------------------------------- /Common/SKKLocalDictionaryFile.swift: -------------------------------------------------------------------------------- 1 | // L辞書などの固定辞書。 2 | // 3 | // ユーザ辞書と異なり 4 | // - 単語登録などのメソッドを持たない 5 | // - 各単語がソートされている。 6 | // といった特徴を持つ。 7 | class SKKLocalDictionaryFile : SKKDictionaryFile { 8 | fileprivate let okuriAri : BinarySearch 9 | fileprivate let okuriNasi : BinarySearch 10 | fileprivate let url : URL 11 | fileprivate let filters : [SKKFilter] = [ 12 | IdFilter(), NumberFilter() 13 | ] 14 | init(url : URL){ 15 | self.url = url 16 | let now = Date() 17 | let dictionary = LoadLocalDictionary(url: url) 18 | 19 | self.okuriAri = BinarySearch(entries: dictionary.okuriAri(), reverse: true) 20 | self.okuriNasi = BinarySearch(entries: dictionary.okuriNasi(), reverse: false) 21 | NSLog("loaded (%f)\n", Date().timeIntervalSince(now)) 22 | } 23 | 24 | func find(_ normal : String, okuri : String?) -> [ String ] { 25 | switch okuri { 26 | case .none: 27 | return search(normal + " ", at: self.okuriNasi) 28 | case .some(let okuri): 29 | return search(normal + okuri + " ", at: self.okuriAri) 30 | } 31 | } 32 | 33 | fileprivate func search(_ target : String, at: BinarySearch) -> [String] { 34 | return filters.flatMap { filter in 35 | filter.call(target, binarySearch: at) { 36 | EntryParser(entry: $0).words() 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Common/SKKUserDictionaryFile.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SKKDictionaryUserFile.swift 3 | // FlickSKK 4 | // 5 | // Created by mzp on 2014/10/17. 6 | // Copyright (c) 2014年 BAN Jun. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /* 12 | * ユーザ辞書。並び順について仮定を持たない。 13 | */ 14 | class SKKUserDictionaryFile : SKKDictionaryFile { 15 | // REMARK: Swift dictionary is too slow. So, we need use NSMutableDictionary. 16 | // [String:String]相当の実装になってる 17 | var okuriAri = NSMutableDictionary() 18 | var okuriNasi = NSMutableDictionary() 19 | fileprivate let url : URL 20 | 21 | init(url : URL){ 22 | self.url = url 23 | // TODO: 変なデータが来たら、空で初期化する 24 | var isOkuriAri = true 25 | IOUtil.each(url.path, with: { line -> Void in 26 | guard let s = line else { return } 27 | // toggle 28 | if s.hasPrefix(";; okuri-nasi entries.") { 29 | isOkuriAri = false 30 | } 31 | // skip comment 32 | if(s.hasPrefix(";")) { return } 33 | 34 | switch self.parse(s as NSString) { 35 | case (let x, let y)?: 36 | if isOkuriAri { 37 | self.okuriAri[x] = y 38 | } else { 39 | self.okuriNasi[x] = y 40 | } 41 | case nil: 42 | break 43 | } 44 | }) 45 | } 46 | 47 | func entries() -> [SKKDictionaryEntry] { 48 | var xs : [SKKDictionaryEntry] = [] 49 | 50 | for (k, v) in okuriNasi { 51 | for kanji in EntryParser(entry: v as! String).words() { 52 | xs.append(.skkDictionaryEntry(kanji: kanji, kana: k as! String, okuri: .none)) 53 | } 54 | } 55 | 56 | for (k, v) in okuriAri { 57 | let kana = (k as! String).butLast() 58 | let okuri = (k as! String).last() 59 | for kanji in EntryParser(entry: v as! String).words() { 60 | xs.append(.skkDictionaryEntry(kanji: kanji, kana: kana, okuri: okuri)) 61 | } 62 | } 63 | 64 | xs.sort() 65 | return xs 66 | } 67 | 68 | func find(_ normal : String, okuri : String?) -> [ String ] { 69 | if let entry = dictFor(okuri)[normal + (okuri ?? "")] as! String? { 70 | let parser = EntryParser(entry: entry) 71 | return parser.words() 72 | } else { 73 | return [] 74 | } 75 | } 76 | 77 | func findWith(_ prefix: String) -> [(kana: String, kanji: String)] { 78 | var xs : [(kana: String, kanji: String)] = [] 79 | for (normal, entry) in self.okuriNasi { 80 | let n = normal as! String 81 | if n.hasPrefix(prefix) && n != prefix { 82 | let parser = EntryParser(entry: (entry as! String)) 83 | for word in parser.words() { 84 | xs.append((kana : n, kanji: word)) 85 | } 86 | } 87 | } 88 | return xs 89 | } 90 | 91 | func register(_ normal : String, okuri: String?, kanji: String) { 92 | if(kanji.isEmpty) { return } 93 | let dict : NSMutableDictionary = dictFor(okuri) 94 | let entry : String? = dict[normal + (okuri ?? "")] as! String? 95 | let parser = EntryParser(entry: entry ?? "") 96 | dict[normal + (okuri ?? "")] = parser.append(kanji) 97 | } 98 | 99 | func serialize() { 100 | if let file = LocalFile(url: self.url) { 101 | file.writeln(";; okuri-ari entries.") 102 | for (k,v) in self.okuriAri { 103 | let kana = k as! String 104 | let kanji = v as! String 105 | if !kana.isEmpty { 106 | file.writeln(kana + " " + kanji) 107 | } 108 | } 109 | file.writeln(";; okuri-nasi entries.") 110 | for (k,v) in self.okuriNasi { 111 | let kana = k as! String 112 | let kanji = v as! String 113 | if !kana.isEmpty { 114 | file.writeln(kana + " " + kanji) 115 | } 116 | } 117 | file.close() 118 | } 119 | } 120 | 121 | func unregister(_ entry : SKKDictionaryEntry) { 122 | switch entry { 123 | case .skkDictionaryEntry(kanji: let kanji, kana: let kana, okuri: let okuri): 124 | let key = kana + (okuri ?? "") 125 | if let entry = dictFor(okuri)[key] as! String? { 126 | let parser = EntryParser(entry: entry) 127 | if let x = parser.remove(kanji) { 128 | dictFor(okuri)[key] = x 129 | } else { 130 | dictFor(okuri).removeObject(forKey: key) 131 | } 132 | self.serialize() 133 | } 134 | } 135 | } 136 | 137 | fileprivate func parse(_ line : NSString) -> (String, String)? { 138 | let range = line.range(of: " ") 139 | if range.location == NSNotFound { 140 | return .none 141 | } else { 142 | let kana : NSString = line.substring(to: range.location) as NSString 143 | let kanji : NSString = line.substring(from: range.location + 1) as NSString 144 | return (kana as String, kanji as String) 145 | } 146 | } 147 | 148 | fileprivate func dictFor(_ okuri: String?) -> NSMutableDictionary { 149 | if okuri == .none { 150 | return self.okuriNasi 151 | } else { 152 | return self.okuriAri 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /Common/ThemeColor.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | private struct ColorPair { 4 | var light: UIColor 5 | var dark: UIColor 6 | 7 | var color: UIColor { 8 | .init(dynamicProvider: { 9 | switch $0.userInterfaceStyle { 10 | case .light, .unspecified: return self.light 11 | case .dark: return self.dark 12 | @unknown default: return self.light 13 | } 14 | }) 15 | } 16 | } 17 | 18 | enum ThemeColor { 19 | static let background = ColorPair(light: .white, dark: .black).color 20 | static let keyboardBackground = ColorPair( 21 | light: .white, 22 | dark: UIColor(white: 0.1, alpha: 1.0)).color 23 | static let selectedBackground = ColorPair( 24 | light: UIColor(white: 0.9, alpha: 1.0), 25 | dark: UIColor(white: 0.2, alpha: 1.0)).color 26 | static let highlightedBackground = ColorPair(light: .gray, dark: .gray).color 27 | static let invertedText = background 28 | 29 | static let buttonBackground = ColorPair(light: .white, dark: .darkGray).color 30 | static let controlButtonBackground = ColorPair( 31 | light: .lightGray, 32 | dark: UIColor(white: 0.2, alpha: 1.0)).color 33 | static let buttonText = ColorPair(light: .black, dark: .white).color 34 | static let buttonTextOnFlickPopup = UIColor.black 35 | static let buttonTextDisabled = ColorPair(light: .gray, dark: .gray).color 36 | static let buttonSubText = ColorPair( 37 | light: .lightGray, 38 | dark: UIColor(white: 0.90, alpha: 1.0)).color 39 | static let buttonBorder = ColorPair(light: .gray, dark: keyboardBackground).color 40 | static let buttonHighlighted = ColorPair( 41 | light: UIColor(hue: 0.10, saturation: 0.07, brightness: 0.96, alpha: 1.0), 42 | dark: UIColor(hue: 0.10, saturation: 0.07, brightness: 0.7, alpha: 1.0)).color 43 | static let buttonSelected = ColorPair( 44 | light: UIColor(white: 0.95, alpha: 1.0), 45 | dark: UIColor(white: 0.6, alpha: 1.0)).color 46 | 47 | static let shadow = ColorPair(light: .black, dark: .clear).color 48 | 49 | static let sessionCellBorder = ColorPair( 50 | light: UIColor(white: 0.75, alpha: 1.0), 51 | dark: UIColor(white: 0.3, alpha: 1.0)).color 52 | 53 | static let hudBackground = ColorPair( 54 | light: UIColor(white: 0, alpha: 0.5), 55 | dark: UIColor(white: 1, alpha: 0.3)).color 56 | static let userInteractionMask = UIColor(white: 1, alpha: 0.2) 57 | } 58 | -------------------------------------------------------------------------------- /Common/ja.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "User Dictionary" = "ユーザー辞書"; 2 | "License" = "ライセンス"; 3 | "Okuri-Ari" = "送りあり"; 4 | "Okuri-Nashi" = "送りなし"; 5 | "Setup" = "初期設定"; 6 | "Reset Learn Dictionary" = "学習結果のリセット"; 7 | "How to use" = "使い方"; 8 | "Register" = "登録"; 9 | "HowToRegisterWordToUserDictionary" = "ユーザー辞書への単語登録はキーボードからも行なえます。\n1. 変換時にspaceをタップして変換候補が無い場合、[登録:]モードに入ります。\n2. 登録したい単語を入力し「⏎」をタップすると登録されます。\n\n※キーボードのフルアクセスが設定アプリで許可されていない場合は、ユーザー辞書の保存やFlickSKKアプリから参照することはできません。\n\n単語の削除は左スワイプで行なえます。"; 10 | "%d words registered" = "%d単語登録済み"; 11 | "word" = "単語"; 12 | "okuri" = "おくり"; 13 | "yomi" = "よみ"; 14 | "NextCandidate" = "次候補"; 15 | "SkipPartialCandidate" = "≪"; 16 | "Cancel" = "キャンセル"; 17 | "Reset" = "リセット"; 18 | "EnterWordRegister" = "▼単語登録"; 19 | "Additional Dictionary" = "追加辞書"; 20 | "No dictionaries" = "追加の辞書はありません"; 21 | "%d dictionaries is enabled" = "%d個の辞書を追加済み"; 22 | "AvailableDictionaries" = "辞書をダウンロード"; 23 | "URL" = "URL"; 24 | "Download" = "追加"; 25 | "DownloadError" = "ダウンロードに失敗しました"; 26 | "EncodingError" = "未対応の文字エンコーディングです"; 27 | "InvalidDictionary" = "辞書の形式が正しくありません"; 28 | "DownloadComplete" = "辞書追加完了"; 29 | "%d okuri-ari %d okuri-nasi" = "送りあり: %d件、送りなし: %d件を追加しました。"; 30 | "Downloading" = "ダウンロード中"; 31 | "Validating" = "辞書検証中"; -------------------------------------------------------------------------------- /FlickSKK.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /FlickSKK.xcodeproj/xcshareddata/xcschemes/FlickSKK.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 51 | 52 | 53 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 78 | 80 | 86 | 87 | 88 | 89 | 95 | 97 | 103 | 104 | 105 | 106 | 108 | 109 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /FlickSKK.xcodeproj/xcshareddata/xcschemes/FlickSKKKeyboard.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 6 | 9 | 10 | 16 | 22 | 23 | 24 | 30 | 36 | 37 | 38 | 39 | 40 | 45 | 46 | 52 | 53 | 54 | 55 | 57 | 63 | 64 | 65 | 66 | 67 | 81 | 84 | 85 | 86 | 92 | 93 | 94 | 97 | 98 | 99 | 107 | 109 | 115 | 116 | 117 | 118 | 120 | 121 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /FlickSKK.xcodeproj/xcshareddata/xcschemes/Memo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 54 | 56 | 62 | 63 | 64 | 65 | 71 | 73 | 79 | 80 | 81 | 82 | 84 | 85 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /FlickSKK.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /FlickSKK.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /FlickSKK/AdditionalDictionaries.swift: -------------------------------------------------------------------------------- 1 | // 追加辞書の情報一覧を管理する 2 | // 3 | // 設定画面で表示したりする、有効な辞書一覧や追加できる辞書一覧を取得する。 4 | class AdditionalDictionaries { 5 | typealias Entry = (title: String, url: URL?, local: URL?) 6 | 7 | fileprivate let defaultDictionaries : [Entry] = [ 8 | (title: "人名辞書", 9 | url: URL(string: "http://openlab.jp/skk/skk/dic/SKK-JISYO.jinmei"), 10 | local: nil), 11 | (title: "郵便番号辞書", 12 | url: URL(string: "http://openlab.jp/skk/skk/dic/zipcode/SKK-JISYO.zipcode"), 13 | local: nil), 14 | (title: "沖縄辞書", 15 | url: URL(string: "http://openlab.jp/skk/skk/dic/SKK-JISYO.okinawa"), 16 | local: nil), 17 | (title: "絵文字辞書", 18 | url: URL(string: "https://raw.githubusercontent.com/uasi/skk-emoji-jisyo/master/SKK-JISYO.emoji.utf8"), 19 | local: nil), 20 | (title: "その他(URL指定)", url: nil, local: nil) 21 | ] 22 | 23 | // すでに追加した辞書のファイル名 24 | fileprivate lazy var dictionaryFiles = SKKDictionary.additionalDictionaries() 25 | 26 | // 有効になった(追加した)辞書一覧の取得 27 | func enabledDictionaries() -> [Entry] { 28 | return dictionaryFiles.map { url in 29 | self.dictionaryForURL(url as URL).map { 30 | self.copy($0, local: url as URL) 31 | } ?? (title: url.lastPathComponent, url: nil, local: url as URL) 32 | } 33 | } 34 | 35 | // 有効にできる辞書一覧を取得する 36 | func availableDictionaries() -> [Entry] { 37 | let names : [String] = dictionaryFiles.compactMap { $0.lastPathComponent } 38 | return defaultDictionaries.filter { entry in 39 | !names.contains(entry.url?.lastPathComponent ?? "") 40 | } 41 | } 42 | 43 | fileprivate func dictionaryForURL(_ url : URL) -> Entry? { 44 | let name = url.lastPathComponent 45 | for entry in defaultDictionaries { 46 | if entry.url?.lastPathComponent == name { 47 | return entry 48 | } 49 | } 50 | return nil 51 | } 52 | 53 | fileprivate func copy(_ entry : Entry, local : URL) -> Entry { 54 | // XXX: 名前付きタプルのうち、一部だけを書き換える。そのうちシンタックスが搭載されると信じてる。 55 | return (title: entry.title, url: entry.url, local: local) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /FlickSKK/AdditionalDictionaryViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | // 追加辞書一覧を表示する画面。 4 | // ダウンロード済みの辞書一覧の表示、削除、追加といったことをできるようにする。 5 | // 6 | // 辞書の更新は、URLをどこに保持するかが難しいので、現バージョンは対応しない。 7 | class AdditionalDictionaryViewController: UITableViewController { 8 | fileprivate var entries : [AdditionalDictionaries.Entry] = [] 9 | fileprivate var dictionaries : [AdditionalDictionaries.Entry] = [] 10 | 11 | init() { 12 | super.init(style: .grouped) 13 | } 14 | 15 | required init?(coder aDecoder: NSCoder) { 16 | fatalError("init(coder:) has not been implemented") 17 | } 18 | 19 | override func viewDidLoad() { 20 | super.viewDidLoad() 21 | 22 | self.title = NSLocalizedString("Additional Dictionary", comment: "") 23 | 24 | self.reloadEntries() 25 | 26 | NotificationCenter.default.addObserver(self, selector: #selector(applicationDidBecomeActive(_:)), name: UIApplication.didBecomeActiveNotification, object: nil) 27 | } 28 | 29 | // MARK: - Entries 30 | 31 | fileprivate func reloadEntries() { 32 | let action = AdditionalDictionaries() 33 | self.entries = action.enabledDictionaries() 34 | self.dictionaries = action.availableDictionaries() 35 | self.tableView.reloadData() 36 | } 37 | 38 | @objc func applicationDidBecomeActive(_ notification: Notification) { 39 | self.reloadEntries() 40 | } 41 | 42 | // MARK: - Table View 43 | override func numberOfSections(in tableView: UITableView) -> Int { 44 | return 2 // entries + quickadd 45 | } 46 | 47 | override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { 48 | switch section { 49 | case 0: return nil 50 | case 1: return NSLocalizedString("AvailableDictionaries", comment: "") 51 | default: return nil 52 | } 53 | } 54 | 55 | override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { 56 | switch section { 57 | case 0: 58 | if self.entries.isEmpty { 59 | return NSLocalizedString("No dictionaries", comment: "") 60 | } else { 61 | return NSString(format: NSLocalizedString("%d dictionaries is enabled", comment: "") as NSString, self.entries.count) as String 62 | } 63 | default: return nil 64 | } 65 | } 66 | 67 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 68 | switch section { 69 | case 0: return self.entries.count 70 | case 1: return self.dictionaries.count 71 | default: return 0 72 | } 73 | } 74 | 75 | fileprivate let kCellID = "Cell" 76 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 77 | let cell = tableView.dequeueReusableCell(withIdentifier: kCellID) ?? UITableViewCell(style: .default, reuseIdentifier: kCellID) 78 | 79 | switch indexPath.section { 80 | case 0: 81 | cell.selectionStyle = .none 82 | cell.textLabel?.text = self.entries[indexPath.row].title 83 | cell.accessoryType = .none 84 | case 1: 85 | cell.textLabel?.text = self.dictionaries[indexPath.row].title 86 | cell.accessoryType = .disclosureIndicator 87 | default: 88 | fatalError("section > 2 has not been implemented") 89 | } 90 | 91 | return cell 92 | } 93 | 94 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 95 | tableView.deselectRow(at: indexPath, animated: true) 96 | 97 | switch indexPath.section { 98 | case 1: 99 | self.navigationController?.pushViewController(DownloadDictionaryViewController(url: dictionaries[indexPath.row].url, 100 | done: { 101 | self.reloadEntries() 102 | }), animated: true) 103 | default: 104 | () 105 | } 106 | } 107 | 108 | override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { 109 | if indexPath.section == 0 && editingStyle == .delete { 110 | if let local = self.entries[indexPath.row].local { 111 | _ = try? FileManager.default.removeItem(at: local as URL) 112 | self.reloadEntries() 113 | } 114 | } 115 | } 116 | 117 | override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle { 118 | if indexPath.section == 0 { 119 | return .delete 120 | } else { 121 | return .none 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /FlickSKK/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // FlickSKK 4 | // 5 | // Created by BAN Jun on 2014/09/27. 6 | // Copyright (c) 2014年 BAN Jun. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | window = UIWindow(frame: UIScreen.main.bounds) 19 | window?.rootViewController = UINavigationController(rootViewController: MainMenuViewController()) 20 | window?.makeKeyAndVisible() 21 | 22 | return true 23 | } 24 | 25 | func applicationWillResignActive(_ application: UIApplication) { 26 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 27 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 28 | } 29 | 30 | func applicationDidEnterBackground(_ application: UIApplication) { 31 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 32 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 33 | } 34 | 35 | func applicationWillEnterForeground(_ application: UIApplication) { 36 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 37 | } 38 | 39 | func applicationDidBecomeActive(_ application: UIApplication) { 40 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 41 | } 42 | 43 | func applicationWillTerminate(_ application: UIApplication) { 44 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 45 | } 46 | 47 | 48 | } 49 | 50 | -------------------------------------------------------------------------------- /FlickSKK/DictionaryInfo.swift: -------------------------------------------------------------------------------- 1 | class DictionaryInfo { 2 | fileprivate let dictionary : LoadLocalDictionary 3 | 4 | init(dictionary : LoadLocalDictionary) { 5 | self.dictionary = dictionary 6 | } 7 | 8 | func okuriAri() -> Int { 9 | return self.dictionary.okuriAri().count 10 | } 11 | 12 | func okuriNasi() -> Int { 13 | return self.dictionary.okuriNasi().count 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /FlickSKK/DownloadDictionary.swift: -------------------------------------------------------------------------------- 1 | // URLで指定された辞書をダウンロードし、FlickSKKで利用できるように整形する。 2 | // 3 | // おもに以下の処理を行なう。 4 | // 1. ダウンロード 5 | // 2. UTF-8への変換 6 | // 3. 辞書の再ソート(※辞書はいわゆる「辞書順」で並んでいるため、文字コードを変換した後は、再ソートが必要。) 7 | // 8 | // もしかしたらダウンロード済みの辞書を統合したほうが高速化ができるかもしれないが、 9 | // とりあえず現バージョンでは対応しない。 10 | class DownloadDictionary { 11 | fileprivate let remote : URL 12 | fileprivate let local : URL 13 | 14 | // MARK: - handler 15 | // FIXME: delegateにしたほうがiOSっぽいので直したほうがいい? 16 | // 辞書追加に成功した際の処理 17 | var success : ((DictionaryInfo)->Void)? 18 | 19 | // 辞書追加でエラーが発生した際の処理 20 | var error : ((String, Error?)->Void)? 21 | 22 | // ダウンロードが進捗した際の処理 23 | var progress : ((String, Float) -> Void)? 24 | 25 | // MARK: - 26 | 27 | init(url : URL) { 28 | self.remote = url 29 | 30 | let local = DictionarySettings.additionalDictionaryURL() 31 | try! FileManager.default.createDirectory(at: local as URL, withIntermediateDirectories: true, attributes: nil) 32 | self.local = local.appendingPathComponent(url.lastPathComponent) 33 | } 34 | 35 | func call() { 36 | let downloadFile = Tempfile.temp() 37 | let utf8File = Tempfile.temp() 38 | 39 | // ダウンロード 40 | save(self.remote, path: downloadFile) { 41 | switch $0 { 42 | case .success: 43 | do { 44 | // UTF8へのエンコード 45 | try self.encodeToUTF8(downloadFile as URL, dest: utf8File as URL) 46 | 47 | // メインスレッドはプログラスバーの更新を行なうので辞書の検証等は別スレッドで行なう。 48 | async { 49 | let dictionary = LoadLocalDictionary(url: utf8File) 50 | 51 | // 妥当性のチェック 52 | if self.validate(dictionary) { 53 | // 再ソート 54 | SortDictionary(dictionary: dictionary).call(self.local) 55 | 56 | // 結果のサマリを渡す 57 | let info = DictionaryInfo(dictionary: dictionary) 58 | self.success?(info) 59 | } else { 60 | self.error?(NSLocalizedString("InvalidDictionary", comment:""), nil) 61 | } 62 | } 63 | } catch let e { 64 | self.error?(NSLocalizedString("EncodingError", comment:""), e) 65 | } 66 | case .failure(let e): 67 | self.error?(NSLocalizedString("DownloadError", comment:""), e) 68 | } 69 | } 70 | } 71 | 72 | // URLを特定ファイルに保存する。 73 | fileprivate func save(_ url : URL, path: URL, completion: @escaping (Result) -> Void) { 74 | var observation: NSKeyValueObservation? 75 | let task = URLSession.shared.downloadTask(with: url) { url, response, error in 76 | observation?.invalidate() 77 | if let error = error { 78 | completion(.failure(error)) 79 | } 80 | guard let url = url else { fatalError() } 81 | do { 82 | try FileManager.default.createDirectory(at: path.deletingLastPathComponent(), withIntermediateDirectories: true, attributes: nil) 83 | try FileManager.default.moveItem(at: url, to: path) 84 | completion(.success(())) 85 | } catch { 86 | completion(.failure(error)) 87 | } 88 | } 89 | observation = task.progress.observe(\.fractionCompleted) { progress, _ in 90 | self.progress?(NSLocalizedString("Downloading", comment:""), Float(progress.fractionCompleted) / 2.0) 91 | } 92 | task.resume() 93 | } 94 | 95 | // UTF8でエンコードして、保存する 96 | fileprivate func encodeToUTF8(_ src : URL, dest: URL) throws { 97 | let content = try readFile(src) 98 | if let file = LocalFile(url: dest) { 99 | defer { file.close() } 100 | file.write(content as String) 101 | } 102 | } 103 | 104 | // ファイルをEUC-JPもしくはUTF-8として読み込む 105 | // (シミュレータではEUC-JPの読み込みは失敗する) 106 | fileprivate func readFile(_ url : URL) throws -> NSString { 107 | if let content = try? NSString(contentsOf: url, encoding: String.Encoding.japaneseEUC.rawValue) { 108 | return content 109 | } 110 | return try NSString(contentsOf: url, encoding: String.Encoding.utf8.rawValue) 111 | } 112 | 113 | // 辞書の検証をする 114 | // 検証の進捗状況は逐次表示する 115 | fileprivate func validate(_ dictionary : LoadLocalDictionary) -> Bool { 116 | let validate = ValidateDictionary(dictionary: dictionary) 117 | validate.progress = { (current, total) in 118 | let progress = Float(current) / Float(total) 119 | self.progress?(NSLocalizedString("Validating", comment:""), progress / 2 + 0.5) 120 | } 121 | return validate.call() 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /FlickSKK/DownloadDictionaryViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | // URL指定で追加辞書のダウンロードを行なうための画面。 4 | // ダウンロード処理自体は DownloadDictionary にまかせているので、画面表示だけを行なえばいい。 5 | // 6 | // プログレスバーでの進捗表示をしようかと思ったが、3G回線でもほぼ待ち時間なしでダウンロードできたので 7 | // とりあえずあとまわしにしている。 8 | class DownloadDictionaryViewController : UITableViewController, UITextFieldDelegate { 9 | fileprivate let urlField = UITextField(frame: CGRect.zero) 10 | fileprivate lazy var doneButton : UIBarButtonItem = UIBarButtonItem( 11 | title: NSLocalizedString("Download", comment:""), 12 | style: .done, target:self, action: #selector(DownloadDictionaryViewController.download)) 13 | fileprivate let done : () -> Void 14 | 15 | init(url : URL?, done : @escaping () -> Void) { 16 | self.done = done 17 | super.init(style: .grouped) 18 | urlField.text = url?.absoluteString ?? "" 19 | self.doneButton.isEnabled = canDownload() 20 | self.navigationItem.rightBarButtonItem = doneButton 21 | } 22 | 23 | required init?(coder aDecoder: NSCoder) { 24 | fatalError("init(coder:) has not been implemented") 25 | } 26 | 27 | // MARK: table view 28 | 29 | let kCellID = "Cell" 30 | 31 | override func numberOfSections(in tableView: UITableView) -> Int { 32 | return 1 33 | } 34 | 35 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 36 | return 1 37 | } 38 | 39 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 40 | let cell = tableView.dequeueReusableCell(withIdentifier: kCellID) ?? UITableViewCell(style: .default, reuseIdentifier: kCellID) 41 | 42 | cell.textLabel?.text = NSLocalizedString("URL", comment:"") 43 | urlField.frame = CGRect(x: 0, y: 0, width: cell.frame.width - 80, height: 130) 44 | urlField.clearButtonMode = .whileEditing 45 | urlField.placeholder = "http://openlab.jp/skk/skk/dic/SKK-JISYO.jinmei" 46 | urlField.contentVerticalAlignment = .center 47 | urlField.delegate = self 48 | urlField.addTarget(self, action: #selector(DownloadDictionaryViewController.didChange as (DownloadDictionaryViewController) -> () -> ()), for: .editingChanged) 49 | cell.accessoryView = urlField 50 | return cell 51 | } 52 | 53 | override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 54 | return 50.0 55 | } 56 | 57 | func textFieldShouldReturn(_ textField: UITextField) -> Bool { 58 | download() 59 | return true 60 | } 61 | 62 | // MARK: done button 63 | @objc fileprivate func didChange() { 64 | self.doneButton.isEnabled = canDownload() 65 | } 66 | 67 | fileprivate func canDownload() -> Bool { 68 | return self.urlField.text?.isEmpty == false 69 | } 70 | 71 | @objc fileprivate func download() { 72 | if canDownload() { 73 | guard let url = self.urlField.text.flatMap({ URL(string: $0)}) else { return } 74 | 75 | let vc = HeadUpProgressViewController() 76 | let action = DownloadDictionary(url: url) 77 | 78 | action.progress = { (title, progress) in 79 | vc.text = title 80 | vc.progress = progress 81 | } 82 | action.success = { info in 83 | vc.close { 84 | self.alert(NSLocalizedString("DownloadComplete", comment:""), 85 | message: NSString(format: NSLocalizedString("%d okuri-ari %d okuri-nasi", comment:"") as NSString, info.okuriAri(), info.okuriNasi()) as String) { 86 | _ = self.navigationController?.popViewController(animated: true) 87 | self.done() 88 | } 89 | } 90 | } 91 | action.error = { (title, e) in 92 | vc.close { 93 | self.alert(title, message: e.map { String(describing: $0) } ?? "" ) 94 | } 95 | } 96 | action.call() 97 | present(vc, animated: true, completion: nil) 98 | } 99 | } 100 | 101 | // アラートメッセージを表示する 102 | fileprivate func alert(_ title: String, message: String, completion: (() -> Void)? = nil) { 103 | let ac = UIAlertController(title: title, message: message, preferredStyle: .alert) 104 | ac.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: { _ in completion?() })) 105 | present(ac, animated: true, completion: nil) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /FlickSKK/FlickSKK.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.application-groups 6 | 7 | group.$(APP_IDENTIFIER) 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /FlickSKK/HeadUpProgressViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import NorthLayout 3 | 4 | // プログレスバーを全面に半透明で表示する。 5 | // 6 | // 途中キャンセルとかあったほうがいいんだけど、面倒なのであとまわし。 7 | // それほどサイズが大きい辞書をDLしないだろうし、たぶん問題になることはすくないはず。 8 | class HeadUpProgressViewController: UIViewController { 9 | fileprivate let progressView : UIProgressView 10 | fileprivate let label = UILabel() 11 | 12 | var progress : Float? { 13 | didSet { 14 | self.updateProgress() 15 | } 16 | } 17 | 18 | var text : String? 19 | 20 | init() { 21 | progressView = UIProgressView() 22 | 23 | super.init(nibName: nil, bundle: nil) 24 | 25 | modalPresentationStyle = .overFullScreen 26 | modalTransitionStyle = .crossDissolve 27 | } 28 | 29 | required init?(coder aDecoder: NSCoder) { 30 | fatalError("init(coder:) has not been implemented") 31 | } 32 | 33 | override func loadView() { 34 | super.loadView() 35 | 36 | view.backgroundColor = ThemeColor.hudBackground 37 | 38 | label.textColor = ThemeColor.invertedText 39 | label.textAlignment = .center 40 | 41 | let autolayout = view.northLayoutFormat(["p":8, "h" : 10], 42 | ["progress": progressView, "label" : label]) 43 | autolayout("H:|-p-[progress]-p-|") 44 | autolayout("V:[progress]-p-[label]") 45 | autolayout("H:|-p-[label]-p-|") 46 | 47 | // 画面中央に表示する 48 | self.view.addConstraint(NSLayoutConstraint(item: progressView, attribute: .centerY, relatedBy: .equal, toItem: view, attribute: .centerY, multiplier: 1, constant: 0)) 49 | } 50 | 51 | fileprivate func updateProgress() { 52 | // メインスレッドで更新しないとプログレスバーが反映されない 53 | DispatchQueue.main.async { 54 | self.label.text = self.text 55 | self.progressView.setProgress(self.progress ?? 0.0, animated: true) 56 | } 57 | } 58 | 59 | func close(_ completion: (() -> Void)? = nil) { 60 | DispatchQueue.main.async { 61 | self.dismiss(animated: true, completion: completion) 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /FlickSKK/Images.xcassets/AppIcon.appiconset/AppIcon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/FlickSKK/Images.xcassets/AppIcon.appiconset/AppIcon-1024.png -------------------------------------------------------------------------------- /FlickSKK/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "57x57", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-57x57@1x.png", 49 | "scale" : "1x" 50 | }, 51 | { 52 | "size" : "57x57", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-57x57@2x.png", 55 | "scale" : "2x" 56 | }, 57 | { 58 | "size" : "60x60", 59 | "idiom" : "iphone", 60 | "filename" : "Icon-App-60x60@2x.png", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "size" : "60x60", 65 | "idiom" : "iphone", 66 | "filename" : "Icon-App-60x60@3x.png", 67 | "scale" : "3x" 68 | }, 69 | { 70 | "size" : "20x20", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-20x20@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "20x20", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-20x20@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "29x29", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-29x29@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "29x29", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-29x29@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "40x40", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-40x40@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "40x40", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-40x40@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "76x76", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-76x76@1x.png", 109 | "scale" : "1x" 110 | }, 111 | { 112 | "size" : "76x76", 113 | "idiom" : "ipad", 114 | "filename" : "Icon-App-76x76@2x.png", 115 | "scale" : "2x" 116 | }, 117 | { 118 | "size" : "83.5x83.5", 119 | "idiom" : "ipad", 120 | "filename" : "Icon-App-83.5x83.5@2x.png", 121 | "scale" : "2x" 122 | }, 123 | { 124 | "size" : "1024x1024", 125 | "idiom" : "ios-marketing", 126 | "filename" : "AppIcon-1024.png", 127 | "scale" : "1x" 128 | } 129 | ], 130 | "info" : { 131 | "version" : 1, 132 | "author" : "xcode" 133 | } 134 | } -------------------------------------------------------------------------------- /FlickSKK/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/FlickSKK/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /FlickSKK/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/FlickSKK/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /FlickSKK/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/FlickSKK/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /FlickSKK/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/FlickSKK/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /FlickSKK/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/FlickSKK/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /FlickSKK/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/FlickSKK/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /FlickSKK/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/FlickSKK/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /FlickSKK/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/FlickSKK/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /FlickSKK/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/FlickSKK/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /FlickSKK/Images.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/FlickSKK/Images.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png -------------------------------------------------------------------------------- /FlickSKK/Images.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/FlickSKK/Images.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png -------------------------------------------------------------------------------- /FlickSKK/Images.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/FlickSKK/Images.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /FlickSKK/Images.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/FlickSKK/Images.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /FlickSKK/Images.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/FlickSKK/Images.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /FlickSKK/Images.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/FlickSKK/Images.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /FlickSKK/Images.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/FlickSKK/Images.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /FlickSKK/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /FlickSKK/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | ja_JP 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.7.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 15 23 | ITSAppUsesNonExemptEncryption 24 | 25 | LSRequiresIPhoneOS 26 | 27 | NSAppTransportSecurity 28 | 29 | NSAllowsArbitraryLoads 30 | 31 | 32 | UILaunchStoryboardName 33 | LaunchScreen 34 | UIRequiredDeviceCapabilities 35 | 36 | armv7 37 | 38 | UISupportedInterfaceOrientations 39 | 40 | UIInterfaceOrientationPortrait 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UISupportedInterfaceOrientations~ipad 45 | 46 | UIInterfaceOrientationPortrait 47 | UIInterfaceOrientationPortraitUpsideDown 48 | UIInterfaceOrientationLandscapeLeft 49 | UIInterfaceOrientationLandscapeRight 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /FlickSKK/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /FlickSKK/MainMenuViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // FlickSKK 4 | // 5 | // Created by BAN Jun on 2014/09/27. 6 | // Copyright (c) 2014年 BAN Jun. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class MainMenuViewController: UITableViewController { 12 | typealias row = (title: String, accessoryType: UITableViewCell.AccessoryType, action: () -> Void) 13 | lazy var sections : [(title: String?, rows: [row])] = { 14 | weak var weakSelf = self 15 | return [ 16 | (title: nil, rows: [ 17 | self.item("Setup") { weakSelf?.gotoSetup() }, 18 | self.item("How to use") { weakSelf?.gotoHowToUse() }]), 19 | (title: nil, rows: [ 20 | self.item("User Dictionary") { weakSelf?.gotoUserDictionary() }, 21 | self.item("Additional Dictionary") { weakSelf?.gotoAdditionalDictionary() }]), 22 | (title: nil, rows: [self.item("Reset Learn Dictionary", accessoryType: .none) { 23 | weakSelf?.reset(); return 24 | }]), 25 | (title: nil, rows: [self.item("License") { 26 | weakSelf?.gotoLicense() 27 | }]) 28 | ] 29 | }() 30 | 31 | init() { 32 | super.init(style: .grouped) 33 | } 34 | 35 | // MARK: View Lifecycle 36 | 37 | override func viewDidLoad() { 38 | super.viewDidLoad() 39 | 40 | self.title = NSLocalizedString("FlickSKK", comment: "") 41 | } 42 | 43 | // MARK: - Table View 44 | 45 | override func numberOfSections(in tableView: UITableView) -> Int { 46 | return sections.count 47 | } 48 | 49 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 50 | return sections[section].rows.count 51 | } 52 | 53 | let kCellID = "Cell" 54 | 55 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 56 | let cell = tableView.dequeueReusableCell(withIdentifier: kCellID) ?? UITableViewCell(style: .default, reuseIdentifier: kCellID) 57 | let row = sections[indexPath.section].rows[indexPath.row] 58 | cell.textLabel?.text = row.title 59 | cell.accessoryType = row.accessoryType 60 | return cell 61 | } 62 | 63 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 64 | tableView.deselectRow(at: indexPath, animated: true) 65 | let row = sections[indexPath.section].rows[indexPath.row] 66 | row.action() 67 | } 68 | 69 | fileprivate func item(_ title : String, accessoryType: UITableViewCell.AccessoryType = .disclosureIndicator, action : @escaping () -> Void) -> row { 70 | return (title: NSLocalizedString(title, comment: ""), accessoryType: accessoryType, action: action) 71 | } 72 | 73 | // MARK: - Actions 74 | func gotoSetup() { 75 | if let path = Bundle.main.path(forResource: "Setup", ofType: "html", inDirectory: "html") { 76 | navigationController?.pushViewController(WebViewController(URL: URL(fileURLWithPath: path)), animated: true) 77 | } 78 | } 79 | 80 | 81 | func gotoHowToUse() { 82 | if let path = Bundle.main.path(forResource: "HowToUse", ofType: "html", inDirectory: "html") { 83 | navigationController?.pushViewController(WebViewController(URL: URL(fileURLWithPath: path)), animated: true) 84 | } 85 | } 86 | 87 | func gotoSettings() { 88 | 89 | } 90 | 91 | func gotoAdditionalDictionary() { 92 | navigationController?.pushViewController(AdditionalDictionaryViewController(), animated: true) 93 | } 94 | 95 | func gotoUserDictionary() { 96 | navigationController?.pushViewController(UserDictionaryViewController(), animated: true) 97 | } 98 | 99 | func reset() { 100 | let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) 101 | alert.popoverPresentationController?.sourceView = view 102 | alert.popoverPresentationController?.sourceRect = view.bounds 103 | alert.popoverPresentationController?.permittedArrowDirections = [] 104 | alert.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel, handler: nil)) 105 | alert.addAction(UIAlertAction(title: NSLocalizedString("Reset", comment: ""), style: .destructive, handler: { action in 106 | SKKDictionary.resetLearnDictionary() 107 | })) 108 | self.present(alert, animated: true, completion: nil) 109 | } 110 | 111 | func gotoLicense() { 112 | if let path = Bundle.main.path(forResource: "License", ofType: "html", inDirectory: "html") { 113 | navigationController?.pushViewController(WebViewController(URL: URL(fileURLWithPath: path)), animated: true) 114 | } 115 | } 116 | 117 | // MARK: - 118 | 119 | required init?(coder aDecoder: NSCoder) { 120 | fatalError("init(coder:) has not been implemented") 121 | } 122 | } 123 | 124 | -------------------------------------------------------------------------------- /FlickSKK/Settings.bundle/Acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | MIT LICENSE Found in the repo 18 | License 19 | MIT 20 | Title 21 | AppGroup 22 | Type 23 | PSGroupSpecifier 24 | 25 | 26 | FooterText 27 | The MIT License (MIT) 28 | 29 | Copyright (c) 2014 Kåre Morstøl, NotTooBad Software (nottoobadsoftware.com) 30 | 31 | Permission is hereby granted, free of charge, to any person obtaining a copy 32 | of this software and associated documentation files (the "Software"), to deal 33 | in the Software without restriction, including without limitation the rights 34 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 35 | copies of the Software, and to permit persons to whom the Software is 36 | furnished to do so, subject to the following conditions: 37 | 38 | The above copyright notice and this permission notice shall be included in all 39 | copies or substantial portions of the Software. 40 | 41 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 42 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 43 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 44 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 45 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 46 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 47 | SOFTWARE. 48 | 49 | License 50 | MIT 51 | Title 52 | FootlessParser 53 | Type 54 | PSGroupSpecifier 55 | 56 | 57 | FooterText 58 | Copyright (c) 2015 banjun <banjun@gmail.com> 59 | 60 | Permission is hereby granted, free of charge, to any person obtaining a copy 61 | of this software and associated documentation files (the "Software"), to deal 62 | in the Software without restriction, including without limitation the rights 63 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 64 | copies of the Software, and to permit persons to whom the Software is 65 | furnished to do so, subject to the following conditions: 66 | 67 | The above copyright notice and this permission notice shall be included in 68 | all copies or substantial portions of the Software. 69 | 70 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 71 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 72 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 73 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 74 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 75 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 76 | THE SOFTWARE. 77 | 78 | License 79 | MIT 80 | Title 81 | NorthLayout 82 | Type 83 | PSGroupSpecifier 84 | 85 | 86 | FooterText 87 | Copyright (c) 2015 banjun <banjun@gmail.com> 88 | 89 | Permission is hereby granted, free of charge, to any person obtaining a copy 90 | of this software and associated documentation files (the "Software"), to deal 91 | in the Software without restriction, including without limitation the rights 92 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 93 | copies of the Software, and to permit persons to whom the Software is 94 | furnished to do so, subject to the following conditions: 95 | 96 | The above copyright notice and this permission notice shall be included in 97 | all copies or substantial portions of the Software. 98 | 99 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 100 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 101 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 102 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 103 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 104 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 105 | THE SOFTWARE. 106 | 107 | License 108 | MIT 109 | Title 110 | ※ikemen 111 | Type 112 | PSGroupSpecifier 113 | 114 | 115 | FooterText 116 | Generated by CocoaPods - https://cocoapods.org 117 | Title 118 | 119 | Type 120 | PSGroupSpecifier 121 | 122 | 123 | StringsTable 124 | Acknowledgements 125 | Title 126 | Acknowledgements 127 | 128 | 129 | -------------------------------------------------------------------------------- /FlickSKK/Settings.bundle/Root.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | Type 9 | PSTitleValueSpecifier 10 | Title 11 | Version 12 | Key 13 | version 14 | DefaultValue 15 | 16 | 17 | 18 | Type 19 | PSChildPaneSpecifier 20 | Title 21 | Acknowledgements 22 | File 23 | Acknowledgements 24 | 25 | 26 | StringsTable 27 | Root 28 | 29 | 30 | -------------------------------------------------------------------------------- /FlickSKK/Settings.bundle/en.lproj/Root.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/FlickSKK/Settings.bundle/en.lproj/Root.strings -------------------------------------------------------------------------------- /FlickSKK/Settings.bundle/ja.lproj/Root.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/FlickSKK/Settings.bundle/ja.lproj/Root.strings -------------------------------------------------------------------------------- /FlickSKK/SortDictionary.swift: -------------------------------------------------------------------------------- 1 | // 辞書を再ソートする。 2 | // 辞書ファイルを読み込み、再ソートを行なった上で、保存する。 3 | // 4 | // 辞書の文字コードを変換した後などは、再ソートを行なわないと、正しい変換ができない。 5 | // skkdic-sortコマンドと同等の処理だが、コードレベルでの共通点はない。 6 | class SortDictionary { 7 | fileprivate let dictionary : LoadLocalDictionary 8 | init(dictionary : LoadLocalDictionary) { 9 | self.dictionary = dictionary 10 | } 11 | 12 | func call(_ dest : URL) { 13 | if let file = LocalFile(url: dest) { 14 | file.writeln(";; okuri-ari entries.") 15 | for line in sorted(dictionary.okuriAri(), reverse: true) { 16 | let line2 = line as! String 17 | file.writeln(line2 ) 18 | } 19 | 20 | file.writeln(";; okuri-nasi entries.") 21 | for line in sorted(dictionary.okuriNasi(), reverse: false) { 22 | let line2 = line as! String 23 | file.writeln(line2) 24 | } 25 | file.close() 26 | } 27 | } 28 | 29 | fileprivate func sorted(_ xs : NSArray, reverse: Bool) -> NSArray { 30 | return xs.sortedArray(comparator: { (x1, y1) -> ComparisonResult in 31 | let x2 = x1 as! NSString 32 | let y2 = y1 as! NSString 33 | if reverse { 34 | return y2.compare(x2 as String, options: .literal) 35 | } else { 36 | return x2.compare(y2 as String, options: .literal) 37 | } 38 | }) as NSArray 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /FlickSKK/Tempfile.swift: -------------------------------------------------------------------------------- 1 | class Tempfile { 2 | fileprivate static var count = 0 3 | 4 | class func temp() -> URL { 5 | count += 1 6 | 7 | let dir = NSTemporaryDirectory() 8 | let basename = NSString(format: "temp%.0f-%d.txt", Date.timeIntervalSinceReferenceDate * 1000.0, count) 9 | return URL(fileURLWithPath: dir).appendingPathComponent(basename as String) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /FlickSKK/UserDictionaryViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserDictionaryViewController.swift 3 | // FlickSKK 4 | // 5 | // Created by BAN Jun on 2014/10/19. 6 | // Copyright (c) 2014年 BAN Jun. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class UserDictionaryViewController: UITableViewController { 12 | fileprivate var entries : [SKKDictionaryEntry] = [] 13 | 14 | init() { 15 | super.init(style: .grouped) 16 | } 17 | 18 | deinit { 19 | NotificationCenter.default.removeObserver(self) 20 | } 21 | 22 | override func viewDidLoad() { 23 | super.viewDidLoad() 24 | 25 | self.title = NSLocalizedString("User Dictionary", comment: "") 26 | 27 | let addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(UserDictionaryViewController.openWordRegister)) 28 | self.navigationItem.rightBarButtonItem = addButton 29 | 30 | self.reloadEntries() 31 | 32 | NotificationCenter.default.addObserver(self, selector: #selector(applicationDidBecomeActive(_:)), name: UIApplication.didBecomeActiveNotification, object: nil) 33 | } 34 | 35 | fileprivate func reloadEntries() { 36 | self.entries = SKKDictionary.defaultUserDictionary().entries() 37 | self.tableView.reloadData() 38 | } 39 | 40 | @objc func applicationDidBecomeActive(_ notification: Notification) { 41 | self.reloadEntries() 42 | } 43 | 44 | // MARK: - Table View 45 | override func numberOfSections(in tableView: UITableView) -> Int { 46 | return 2 // description + entries 47 | } 48 | 49 | override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { 50 | switch section { 51 | case 0: return NSLocalizedString("HowToRegisterWordToUserDictionary", comment: "") 52 | case 1: return NSString(format: NSLocalizedString("%d words registered", comment: "") as NSString, self.entries.count) as String 53 | default: return nil 54 | } 55 | } 56 | 57 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 58 | switch section { 59 | case 1: return self.entries.count 60 | default: return 0 61 | } 62 | } 63 | 64 | fileprivate let kCellID = "Cell" 65 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 66 | let cell = tableView.dequeueReusableCell(withIdentifier: kCellID) ?? UITableViewCell(style: .default, reuseIdentifier: kCellID) 67 | cell.selectionStyle = .none 68 | 69 | switch self.entries[indexPath.row] { 70 | case .skkDictionaryEntry(kanji: let kanji, kana: let kana, okuri: let okuri): 71 | let o = okuri ?? "" 72 | cell.textLabel?.text = "\(kanji): \(kana)\(o)" 73 | } 74 | 75 | return cell 76 | } 77 | 78 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 79 | tableView.deselectRow(at: indexPath, animated: true) 80 | 81 | // TODO: something 82 | } 83 | 84 | override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { 85 | if editingStyle == .delete { 86 | let entry = self.entries[indexPath.row] 87 | SKKDictionary.defaultUserDictionary().unregister(entry) 88 | self.reloadEntries() 89 | } 90 | } 91 | 92 | // MARK: - 93 | required init?(coder aDecoder: NSCoder) { 94 | fatalError("init(coder:) has not been implemented") 95 | } 96 | 97 | @objc fileprivate func openWordRegister() { 98 | let controller = WordRegisterViewController() 99 | controller.done = {(word, okuri, yomi) in 100 | let dict = SKKDictionary.defaultUserDictionary() 101 | dict.register(yomi, okuri: okuri, kanji: word) 102 | dict.serialize() 103 | self.reloadEntries() 104 | } 105 | self.navigationController?.pushViewController(controller, animated: true) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /FlickSKK/ValidateDictionary.swift: -------------------------------------------------------------------------------- 1 | // 辞書の妥当性をチェックする。 2 | // 3 | // 厳密なチェックはできないので、FlickSKK内で扱えるかどうかを簡易的にチェックする 4 | class ValidateDictionary { 5 | fileprivate let dictionary : LoadLocalDictionary 6 | fileprivate let total : Int 7 | fileprivate var current : Int = 0 8 | var progress : ((Int, Int) -> Void)? 9 | 10 | init(dictionary : LoadLocalDictionary) { 11 | self.dictionary = dictionary 12 | self.total = self.dictionary.count() 13 | } 14 | 15 | func call() -> Bool { 16 | return validate(self.dictionary.okuriAri()) && validate(self.dictionary.okuriNasi()) 17 | } 18 | 19 | fileprivate func validate(_ xs : NSArray) -> Bool { 20 | for x in xs { 21 | let entry = EntryParser(entry: x as! String) 22 | if entry.title() == nil || entry.words() == [] { 23 | return false 24 | } 25 | updateProgress() 26 | } 27 | return true 28 | } 29 | 30 | fileprivate func updateProgress() { 31 | current += 1 32 | if current % 100 == 0 { 33 | self.progress?(current, total) 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /FlickSKK/WebViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WebViewController.swift 3 | // FlickSKK 4 | // 5 | // Created by BAN Jun on 2014/10/19. 6 | // Copyright (c) 2014年 BAN Jun. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Ikemen 11 | import WebKit 12 | 13 | class WebViewController: UIViewController, WKNavigationDelegate { 14 | lazy var configure = WKWebViewConfiguration() ※ { (wc: inout WKWebViewConfiguration) in 15 | wc.dataDetectorTypes = .link 16 | } 17 | lazy var webView: WKWebView = WKWebView(frame: CGRect.zero, configuration: configure) ※ { (wv: inout WKWebView) in 18 | wv.autoresizingMask = [.flexibleWidth, .flexibleHeight] 19 | wv.navigationDelegate = self 20 | } 21 | 22 | var initialURL: URL? 23 | 24 | init(URL: Foundation.URL) { 25 | self.initialURL = URL 26 | super.init(nibName: nil, bundle: nil) 27 | } 28 | 29 | override func loadView() { 30 | self.view = self.webView 31 | view.backgroundColor = ThemeColor.background 32 | webView.isOpaque = false // https://stackoverflow.com/questions/27655930/how-can-i-give-wkwebview-a-colored-background 33 | 34 | if let u = initialURL { 35 | self.webView.load(URLRequest(url: u)) 36 | } 37 | } 38 | 39 | // MARK: WebView Delegate 40 | func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { 41 | if navigationAction.navigationType == .linkActivated { 42 | // Open in Safari 43 | UIApplication.shared.open(navigationAction.request.url!, options: [:]) { _ in } 44 | decisionHandler(.cancel) 45 | return 46 | } 47 | decisionHandler(.allow) 48 | } 49 | 50 | func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { 51 | webView.evaluateJavaScript("document.title") { (result : Any?, _) in 52 | if let title = result as? String { 53 | self.title = title 54 | } 55 | } 56 | } 57 | 58 | // MARK: - 59 | 60 | required init?(coder aDecoder: NSCoder) { 61 | fatalError("init(coder:) has not been implemented") 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /FlickSKK/WordRegisterViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingsViewController.swift 3 | // FlickSKK 4 | // 5 | // Created by MIZUNO Hiroki on 12/18/14. 6 | // Copyright (c) 2014 BAN Jun. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | class WordRegisterViewController : UITableViewController, UITextFieldDelegate { 11 | fileprivate let yomiField = UITextField(frame: CGRect.zero) 12 | fileprivate let okuriField = UITextField(frame: CGRect.zero) 13 | fileprivate let wordField = UITextField(frame: CGRect.zero) 14 | fileprivate lazy var doneButton : UIBarButtonItem = 15 | UIBarButtonItem(title: NSLocalizedString("Register", comment:""), 16 | style: .done, target:self, action: #selector(WordRegisterViewController.register)) 17 | var done : ((String, String?, String) -> Void)? 18 | 19 | fileprivate lazy var sections : [( 20 | title: String?, 21 | rows: [(title: String, text: UITextField, returnType: UIReturnKeyType)] 22 | )] = [ 23 | (title: nil, rows: [ 24 | (title: NSLocalizedString("word", comment: ""), text: self.wordField, returnType: .next), 25 | (title: NSLocalizedString("yomi", comment: ""), text: self.yomiField, returnType: .next), 26 | (title: NSLocalizedString("okuri", comment: ""), text: self.okuriField, returnType: .default), 27 | ])] 28 | 29 | init() { 30 | super.init(style: .grouped) 31 | self.doneButton.isEnabled = false 32 | self.navigationItem.rightBarButtonItem = doneButton 33 | } 34 | 35 | required init?(coder aDecoder: NSCoder) { 36 | fatalError("init(coder:) has not been implemented") 37 | } 38 | 39 | @objc fileprivate func register() { 40 | if canRegister() { 41 | var okuri : String? = nil 42 | 43 | if let text = self.okuriField.text, !text.isEmpty { 44 | // 1文字目 45 | let xs = Array(text) 46 | let first = xs[0] 47 | // ローマ字変換 48 | if let roman = first.toRoman() { 49 | // 1文字目を取得 50 | let rs = Array(roman) 51 | okuri = String(rs[0]) 52 | } else { 53 | okuri = String(first) 54 | } 55 | } 56 | self.done?( 57 | self.wordField.text ?? "", 58 | okuri, 59 | self.yomiField.text ?? "") 60 | _ = self.navigationController?.popViewController(animated: true) 61 | } 62 | } 63 | 64 | let kCellID = "Cell" 65 | 66 | override func numberOfSections(in tableView: UITableView) -> Int { 67 | return sections.count 68 | } 69 | 70 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 71 | return sections[section].rows.count 72 | } 73 | 74 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 75 | let cell = tableView.dequeueReusableCell(withIdentifier: kCellID) ?? UITableViewCell(style: .default, reuseIdentifier: kCellID) 76 | 77 | let row = sections[indexPath.section].rows[indexPath.row] 78 | cell.accessoryType = .none 79 | cell.selectionStyle = .none 80 | 81 | // label 82 | let label = UILabel(frame: CGRect(x: 20, y: 5, width: 130, height: 45)) 83 | label.text = row.title 84 | label.font = Appearance.normalFont(17.0) 85 | cell.contentView.addSubview(label) 86 | 87 | // text field 88 | let textField = row.text 89 | textField.frame = CGRect(x: 130, y: 0, width: view.frame.width-130, height: 50) 90 | textField.font = Appearance.normalFont(17.0) 91 | textField.clearButtonMode = .whileEditing 92 | textField.placeholder = row.title 93 | textField.contentVerticalAlignment = .center 94 | textField.returnKeyType = row.returnType 95 | textField.delegate = self 96 | textField.addTarget(self, action: #selector(didChange(sender:)), for: .editingChanged) 97 | cell.contentView.addSubview(textField) 98 | 99 | return cell 100 | } 101 | 102 | override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 103 | return 50.0 104 | } 105 | 106 | func textFieldShouldReturn(_ textField: UITextField) -> Bool { 107 | for section in self.sections { 108 | for (index,row) in section.rows.enumerated() { 109 | if textField == row.text { 110 | switch row.returnType { 111 | case .next: 112 | section.rows[index+1].text.becomeFirstResponder() 113 | case .default: 114 | register() 115 | default: 116 | // do nothing 117 | break 118 | } 119 | } 120 | } 121 | } 122 | return true 123 | } 124 | 125 | @objc fileprivate func didChange(sender: UITextField) { 126 | self.doneButton.isEnabled = canRegister() 127 | } 128 | 129 | fileprivate func canRegister() -> Bool { 130 | // 登録できる条件 131 | // ・登録する単語が入力されている 132 | // ・よみが入力されている。SKK的に読みはほぼ任意(例: forallとかもある)なので、あまり前提をおけない。 133 | // ・送り仮名が空もしくはひらがな一文字(ローマ字に変換できる) 134 | let wordInputed : Bool = !(self.wordField.text?.isEmpty ?? true) 135 | let yomiInputed : Bool = !(yomiField.text?.isEmpty ?? true) 136 | 137 | let okuriBlank : Bool = self.okuriField.text?.isEmpty ?? true 138 | let okuri = self.okuriField.text 139 | let okuriInputed = okuri?.count == 1 && (okuri?.first?.toRoman() != nil) 140 | 141 | return wordInputed && yomiInputed && (okuriBlank || okuriInputed) 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /FlickSKK/flickskk_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/FlickSKK/flickskk_white.png -------------------------------------------------------------------------------- /FlickSKK/html/HowToUse.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 使い方 8 | 9 | 10 |

使い方

11 |

1. 単文節変換

12 |

「⇧」の直後に入力された文字を文節の始まりとして認識します。

13 |

例えば「変換」は、以下のように入力できます。

14 | 15 |
    16 |
  1. 「⇧」、「へ」、「ん」、「か」、「ん」と押します。
  2. 17 |
  3. 「次候補」を押して変換候補から入力したい単語を選択します。
  4. 18 |
  5. 「⏎」で変換を確定します。
  6. 19 |
20 | 21 |

2. 送り仮名変換

22 |

変換中の「⇧」は、送り仮名の開始として認識されます。

23 |

例えば「晴れ」は、以下のように入力できます。

24 | 25 |
    26 |
  1. 「⇧」、「は」、「⇧」、「れ」と押します。
  2. 27 |
  3. 「次候補」を押して変換候補から入力したい単語を選択します。
  4. 28 |
  5. 「⏎」で変換を確定します。
  6. 29 |
30 | 31 |

3. カタカナ入力

32 |

「かな」を横にフリックすると、カタカナ入力モードになります。 33 | また、変換中に同じ操作をすると、カタカナとして確定されます。

34 | 35 |

同様に、「かな」を上にフリックするとひらかな入力モードに、下にフリックすると半角カナ入力モードになります。

36 | 37 | 38 |

4. 辞書登録

39 |

辞書に登録されていない単語を変換しようとすると、辞書登録モードになります。

40 | 41 |

変換後の単語を入力し、「⏎」を押すことで辞書登録ができます。

42 | 43 |

登録した単語は、FlickSKKアプリの「ユーザー辞書」から確認できます。ただし、フルアクセスの許可が必要です。

44 | 45 |

5. 補完候補

46 |

変換中に2文字以上入力中の場合、変換履歴とユーザー辞書の単語から候補を補完して表示します。

47 |

補完候補は[次候補]を左フリックしてスキップ[≪]できます。

48 | 49 | 50 | -------------------------------------------------------------------------------- /FlickSKK/html/License.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | License 8 | 9 | 10 |

globeアイコン

11 |

Designmodo氏による作品です。CC BY 3.0によって提供されています。 12 |

SKK-JISYO.L

13 |

FlickSKKはSKK-JISYO.Lを元にしたSKK辞書を同梱しています。入手先: https://github.com/codefirst/FlickSKK 14 |

15 | ;; Large size dictionary for SKK system
16 | ;; Copyright (C) 1988-1995, 1997, 1999-2010
17 | ;;
18 | ;; Masahiko Sato <masahiko@kuis.kyoto-u.ac.jp>
19 | ;; Hironobu Takahashi <takahasi@tiny.or.jp>,
20 | ;; Masahiro Doteguchi, Miki Inooka,
21 | ;; Yukiyoshi Kameyama <kameyama@kuis.kyoto-u.ac.jp>,
22 | ;; Akihiko Sasaki, Dai Ando, Junichi Okukawa,
23 | ;; Katsushi Sato and Nobuhiro Yamagishi
24 | ;; NAKAJIMA Mikio <minakaji@osaka.email.ne.jp>
25 | ;; MITA Yuusuke <clefs@mail.goo.ne.jp>
26 | ;; SKK Development Team <skk@ring.gr.jp>
27 | ;;
28 | ;; Maintainer: SKK Development Team <skk@ring.gr.jp>
29 | ;; Version: $Id: SKK-JISYO.L,v 1.1089 2012/09/01 00:10:04 czkmt Exp $
30 | ;; Keywords: japanese
31 | ;; Last Modified: $Date: 2012/09/01 00:10:04 $
32 | ;;
33 | ;; This dictionary is free software; you can redistribute it and/or
34 | ;; modify it under the terms of the GNU General Public License as
35 | ;; published by the Free Software Foundation; either version 2, or
36 | ;; (at your option) any later version.
37 | ;;
38 | ;; This dictionary is distributed in the hope that it will be useful,
39 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
40 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
41 | ;; General Public License for more details.
42 | ;;
43 | ;; You should have received a copy of the GNU General Public License
44 | ;; along with Daredevil SKK, see the file COPYING.  If not, write to
45 | ;; the Free Software Foundation Inc., 59 Temple Place - Suite 330,
46 | ;; Boston, MA 02111-1307, USA.
47 |     
48 |

49 |

追加辞書

50 |

追加辞書の以下のサイトで配布されています。辞書のライセンスは、配布サイトで確認してください。

51 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /FlickSKK/html/Setup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 初期設定 8 | 9 | 10 |

初期設定

11 |

1. iPhoneの「設定」を開く

12 |

設定アイコンをタップし、設定画面を開きます。

13 |

14 |

2. 一般設定画面を開く

15 |

「一般」をタップし、一般設定画面を開く。

16 |

17 |

3. キーボード設定画面を開く

18 |

「キーボード」をタップし、キーボード設定画面を開く。

19 |

20 |

4. キーボード追加設定画面を開く

21 |

「キーボード」をタップし、キーボード追加画面を開く。

22 |

23 |

5. キーボード一覧を表示する

24 |

「新しいキーボードを追加」をタップし、キーボード一覧を表示する。

25 |

26 |

6. FlickSKKを追加する

27 |

「他社製キーボード」にある「FlickSKK」をタップし、FlickSKKを追加する。

28 |

29 |

7. FlickSKK設定画面を開く

30 |

追加された「FlickSKK」をタップし、設定画面を開く

31 |

32 |

8. フルアクセスを許可する

33 |

「フルアクセスへ許可」をオンにする。オンにしなくても基本機能の利用は可能ですが、辞書の編集等が行なえません。

34 |

35 |

9. キーボードを切り替える

36 |

キーボード入力を行なうアプリを起動し、地球マークを長押し、またはタップで切り替えるとFlickSKKが起動します。

37 |

38 | 39 | 40 | -------------------------------------------------------------------------------- /FlickSKK/html/common.css: -------------------------------------------------------------------------------- 1 | body { 2 | word-break: break-all; 3 | font-family: "HiraKakuProN-W3"; 4 | } 5 | 6 | @media (prefers-color-scheme: dark) { 7 | body { 8 | color: white; 9 | background-color: black; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /FlickSKK/html/globe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/FlickSKK/html/globe.png -------------------------------------------------------------------------------- /FlickSKK/html/how_to_use/1-Preferences.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/FlickSKK/html/how_to_use/1-Preferences.png -------------------------------------------------------------------------------- /FlickSKK/html/how_to_use/2-General.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/FlickSKK/html/how_to_use/2-General.png -------------------------------------------------------------------------------- /FlickSKK/html/how_to_use/3-Keyboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/FlickSKK/html/how_to_use/3-Keyboard.png -------------------------------------------------------------------------------- /FlickSKK/html/how_to_use/4-Keyboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/FlickSKK/html/how_to_use/4-Keyboard.png -------------------------------------------------------------------------------- /FlickSKK/html/how_to_use/5-Add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/FlickSKK/html/how_to_use/5-Add.png -------------------------------------------------------------------------------- /FlickSKK/html/how_to_use/6-FlickSKK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/FlickSKK/html/how_to_use/6-FlickSKK.png -------------------------------------------------------------------------------- /FlickSKK/html/how_to_use/7-KeyboardList.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/FlickSKK/html/how_to_use/7-KeyboardList.png -------------------------------------------------------------------------------- /FlickSKK/html/how_to_use/8-FullAccess.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/FlickSKK/html/how_to_use/8-FullAccess.png -------------------------------------------------------------------------------- /FlickSKK/html/how_to_use/9-Change.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/FlickSKK/html/how_to_use/9-Change.png -------------------------------------------------------------------------------- /FlickSKK/iTunesArtwork: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/FlickSKK/iTunesArtwork -------------------------------------------------------------------------------- /FlickSKK/iTunesArtwork@2x: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/FlickSKK/iTunesArtwork@2x -------------------------------------------------------------------------------- /FlickSKKKeyboard/Candidate.swift: -------------------------------------------------------------------------------- 1 | enum Candidate : Equatable { 2 | case partial(kanji: String, kana: String) 3 | case exact(kanji : String) 4 | 5 | var kanji: String { 6 | switch self { 7 | case .partial(kanji: let kanji, kana: _): return kanji 8 | case .exact(kanji: let kanji): return kanji 9 | } 10 | } 11 | 12 | var isPartial: Bool { 13 | switch self { 14 | case .partial(kanji: _, kana: _): return true 15 | default: return false 16 | } 17 | } 18 | } 19 | 20 | func ==(l : Candidate, r : Candidate) -> Bool { 21 | switch (l,r) { 22 | case let (.partial(kanji: kanji1, kana: kana1), .partial(kanji: kanji2, kana: kana2)): 23 | return kanji1 == kanji2 && kana1 == kana2 24 | case let (.exact(kanji: kanji1), .exact(kanji: kanji2)): 25 | return kanji1 == kanji2 26 | default: 27 | return false 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /FlickSKKKeyboard/ComposeMode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ComposeResult.swift 3 | // FlickSKK 4 | // 5 | // Created by MIZUNO Hiroki on 2/7/15. 6 | // Copyright (c) 2015 BAN Jun. All rights reserved. 7 | // 8 | 9 | enum ComposeMode { 10 | case directInput 11 | case kanaCompose(kana : String, candidates: [Candidate]) 12 | case kanjiCompose(kana : String, okuri : String?, candidates: [Candidate], index : Int) 13 | case wordRegister(kana : String, okuri : String?, composeText : String, composeMode : [ComposeMode]) 14 | } 15 | 16 | func ==(l : ComposeMode, r : ComposeMode) -> Bool { 17 | switch (l,r) { 18 | case (.directInput, .directInput): 19 | return true 20 | case (.kanaCompose(kana: let kana1, candidates: let candidates1), .kanaCompose(kana: let kana2, candidates: let candidates2)): 21 | return kana1 == kana2 && candidates1 == candidates2 22 | case (.kanjiCompose(kana : let kana1, okuri : let okuri1, candidates: let candidates1, index: let index1), 23 | .kanjiCompose(kana : let kana2, okuri : let okuri2, candidates: let candidates2, index: let index2)): 24 | return kana1 == kana2 && okuri1 == okuri2 && candidates1 == candidates2 && index1 == index2 25 | case (.wordRegister(kana : let kana1, okuri : let okuri1, composeText : let composeText1, composeMode: let mode1), 26 | .wordRegister(kana : let kana2, okuri : let okuri2, composeText : let composeText2, composeMode: let mode2)): 27 | let m1 : ComposeMode = mode1[0] 28 | let m2 : ComposeMode = mode2[0] 29 | return kana1 == kana2 && okuri1 == okuri2 && composeText1 == composeText2 && m1 == m2 30 | default: 31 | return false 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /FlickSKKKeyboard/ComposeModeFactory.swift: -------------------------------------------------------------------------------- 1 | class ComposeModeFactory { 2 | fileprivate let dictionary : DictionaryEngine 3 | 4 | init(dictionary : DictionaryEngine) { 5 | self.dictionary = dictionary 6 | } 7 | 8 | func kanaCompose(_ kana : String) -> ComposeMode { 9 | let candidates = dictionary.find(kana, okuri: nil, dynamic: kana.utf16.count > 1) 10 | return .kanaCompose(kana : kana, candidates: candidates) 11 | } 12 | 13 | func kanjiCompose(_ kana : String, okuri : String?) -> ComposeMode { 14 | let candidates = dictionary.find(kana, okuri: okuri, dynamic: okuri == nil && kana.utf16.count > 1) 15 | if candidates.isEmpty { 16 | return .wordRegister(kana : kana, okuri : okuri, composeText: "", composeMode : [ .directInput ]) 17 | } else { 18 | return .kanjiCompose(kana: kana, okuri: okuri, candidates: candidates, index: 0) 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /FlickSKKKeyboard/ComposeModePresenter.swift: -------------------------------------------------------------------------------- 1 | // ComposeModeを表示する 2 | class ComposeModePresenter { 3 | /// 入力先のアプリにマーク付きテキストで表示する未確定文字列 4 | func markedText(_ composeMode : ComposeMode) -> String? { 5 | switch composeMode { 6 | case .directInput: 7 | return nil 8 | case .kanaCompose(kana: let kana, candidates: _): 9 | return kana 10 | case .kanjiCompose(kana: _, okuri: _, candidates: let candidates, index: let index): 11 | return candidates[index].kanji 12 | case .wordRegister(kana: _, okuri: _, composeText: let text, composeMode: let m): 13 | let nested = markedText(m[0]) 14 | return text + (nested ?? "") 15 | } 16 | } 17 | 18 | /// 変換中にキーボードの先頭に表示する文字列 19 | func composeText(_ composeMode : ComposeMode) -> String? { 20 | switch composeMode { 21 | case .directInput: 22 | return nil 23 | case .kanaCompose: 24 | return "▽" 25 | case .kanjiCompose: 26 | return "▼" 27 | case .wordRegister(kana: let kana, okuri: let okuri, composeText: let text, composeMode: let m): 28 | let prefix = kana + (okuri.map({ str in "*" + str }) ?? "") 29 | let nested = composeText(m[0]) 30 | return "[登録:\(prefix)]\(text)\(nested ?? "")" 31 | } 32 | } 33 | 34 | // 候補の取得 35 | func candidates(_ composeMode : ComposeMode) -> (candidates: [Candidate], index: Int?)? { 36 | switch composeMode { 37 | case .directInput: 38 | return .none 39 | case .kanaCompose(kana: _, candidates: let candidates): 40 | return (candidates, .none) 41 | case .kanjiCompose(kana: _, okuri: _, candidates: let candidates, index: let index): 42 | return (candidates, index) 43 | case .wordRegister(kana : _, okuri : _, composeText : _, composeMode : let m): 44 | return candidates(m[0]) 45 | } 46 | } 47 | 48 | // スペースか次候補か 49 | func inStatusShowsCandidatesBySpace(_ composeMode : ComposeMode) -> Bool { 50 | switch composeMode { 51 | case .directInput: 52 | return false 53 | case .kanaCompose: 54 | return true 55 | case .kanjiCompose(kana: _, okuri: _, candidates: _, index: _): 56 | return true 57 | case .wordRegister(kana : _, okuri : _, composeText : _, composeMode : let m): 58 | return inStatusShowsCandidatesBySpace(m[0] 59 | ) 60 | } 61 | } 62 | 63 | /// wordRegisterの初期状態か(初期状態に入ったときにhapticするため) 64 | func isOnInitialStateOfWordRegister(_ composeMode : ComposeMode) -> Bool { 65 | switch composeMode { 66 | case .directInput, .kanaCompose, .kanjiCompose: return false 67 | case .wordRegister(kana: _, okuri: _, composeText: let composeText, composeMode: let composeMode): 68 | guard let mode = composeMode.first else { return false } 69 | switch mode { 70 | case .directInput: return composeText.isEmpty 71 | case .kanaCompose, .kanjiCompose, .wordRegister: return isOnInitialStateOfWordRegister(mode) 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /FlickSKKKeyboard/DictionaryEngine.swift: -------------------------------------------------------------------------------- 1 | // SKKの辞書をラップして、フリック入力に適したインターフェースを提供する 2 | class DictionaryEngine { 3 | fileprivate let dictionary : SKKDictionary 4 | init(dictionary : SKKDictionary){ 5 | self.dictionary = dictionary 6 | } 7 | 8 | // 変換結果を学習する 9 | func learn(_ kana : String, okuri : String?, kanji : String) { 10 | // 正規化する 11 | let (k, o) = normalize(kana, okuri: okuri) 12 | 13 | // 学習する 14 | self.dictionary.learn(k, okuri: o, kanji: kanji) 15 | } 16 | 17 | // q-確定の結果を学習する 18 | func partial(_ kana: String, kanji: String) { 19 | // 正規化する 20 | let (k, _) = normalize(kana, okuri: nil) 21 | 22 | // 学習する 23 | self.dictionary.partial(k, okuri: nil, kanji: kanji) 24 | } 25 | 26 | // 辞書を検索する。 27 | // ・ っの特殊ルール等を考慮する 28 | func find(_ kana : String, okuri : String?, dynamic: Bool) -> [Candidate] { 29 | // 結果をストアする 30 | var candidates : [Candidate] = [] 31 | 32 | // 正規化する 33 | let (t, roman) = normalize(kana, okuri: okuri) 34 | 35 | // ダイナミック変換 36 | if dynamic { 37 | for candidate in self.dictionary.findDynamic(kana) { 38 | candidates.append(.partial(kanji: candidate.kanji, kana: candidate.kana)) 39 | } 40 | } 41 | 42 | // 通常の検索をする 43 | for candidate in self.dictionary.find(t, okuri: roman) { 44 | candidates.append(.exact(kanji: candidate + (okuri ?? ""))) 45 | } 46 | 47 | // 末尾が「っ」の場合は、変換位置を1つ前にする 48 | if okuri != .none && t.last() == "っ" { 49 | // 「っ」送り仮名の場合の特殊処理 50 | // https://github.com/codefirst/FlickSKK/issues/27 51 | for candidate in self.dictionary.find(t.butLast(), okuri: roman) { 52 | candidates.append(.exact(kanji: candidate + "っ" + (okuri ?? ""))) 53 | } 54 | } 55 | return candidates 56 | } 57 | 58 | // 変換結果を登録する 59 | func register(_ kana : String, okuri : String?, kanji : String) { 60 | // 正規化する 61 | let (k, o) = normalize(kana, okuri: okuri) 62 | 63 | // 辞書登録 64 | dictionary.register(k, okuri: o, kanji: kanji) 65 | 66 | } 67 | 68 | // SKK辞書用に正規化する 69 | fileprivate func normalize(_ kana : String, okuri : String?) -> (String, String?) { 70 | // 読みをひらかなにする 71 | let t = kana.conv(.hirakana) 72 | 73 | // 送り仮名をローマ字に変換する 74 | let roman : String? = okuri?.first()?.toRoman()?.first().map({ c in String(c) }) 75 | 76 | return (t, roman) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /FlickSKKKeyboard/FlickSKKKeyboard.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.application-groups 6 | 7 | group.$(APP_IDENTIFIER) 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /FlickSKKKeyboard/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | FlickSKK 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | XPC! 19 | CFBundleShortVersionString 20 | 1.7.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 15 25 | NSExtension 26 | 27 | NSExtensionAttributes 28 | 29 | IsASCIICapable 30 | 31 | PrefersRightToLeft 32 | 33 | PrimaryLanguage 34 | ja-JP 35 | RequestsOpenAccess 36 | 37 | 38 | NSExtensionPointIdentifier 39 | com.apple.keyboard-service 40 | NSExtensionPrincipalClass 41 | $(PRODUCT_MODULE_NAME).KeyboardViewController 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /FlickSKKKeyboard/KanaFlickKey.swift: -------------------------------------------------------------------------------- 1 | enum KanaFlickKey { 2 | case seq(String, showSeqs: Bool) 3 | case shift 4 | case `return` 5 | case backspace 6 | case keyboardChange 7 | case inputModeChange([SKKInputMode?]) 8 | case number 9 | case alphabet 10 | case komojiDakuten 11 | case upperLower 12 | case space 13 | case nothing 14 | 15 | // メインの「あ」「A」「1」など 16 | var buttonLabel: String { 17 | switch self { 18 | case let .seq(s, _): return String(s[s.startIndex]) 19 | case .shift: return "⇧" 20 | case .return: return "⏎" 21 | case .backspace: return "⌫" 22 | case .keyboardChange: return "" 23 | case .inputModeChange: return "かな" 24 | case .number: return "123" 25 | case .alphabet: return "ABC" 26 | case .komojiDakuten: return "小゛゜" 27 | case .upperLower: return "a/A" 28 | case .space: return "space" 29 | case .nothing: return "" 30 | } 31 | } 32 | 33 | // 残りの「←↓↑→」など 34 | var additionalButtonLabel: String? { 35 | switch self { 36 | case let .seq(s, true): 37 | let seq = Array(s).map{String($0)} 38 | let left = seq.count > 1 ? seq[1] : " " 39 | let top = seq.count > 2 ? seq[2] : " " 40 | let right = seq.count > 3 ? seq[3] : " " 41 | let bottom = seq.count > 4 ? seq[4] : " " 42 | return left + bottom + top + right // ex. seq "1↓←↑→" -> "←↓↑→" 43 | default: 44 | return nil 45 | } 46 | } 47 | 48 | static let ignoredSequence = "-ignore-" 49 | 50 | var sequence: [String]? { 51 | switch self { 52 | case let .seq(s, _): return Array(s).map{String($0)} 53 | case .inputModeChange: return [KanaFlickKey.ignoredSequence,"_","かな","カナ","カナ"] 54 | case .space: return [KanaFlickKey.ignoredSequence, NSLocalizedString("SkipPartialCandidate", comment: "")] 55 | default: return nil 56 | } 57 | } 58 | 59 | var isControl: Bool { 60 | switch self { 61 | case .seq: return false 62 | default: return true 63 | } 64 | } 65 | 66 | var isRepeat: Bool { 67 | switch self { 68 | case .backspace: return true 69 | default: return false 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /FlickSKKKeyboard/KeyPad.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeyPad.swift 3 | // FlickSKK 4 | // 5 | // Created by BAN Jun on 2014/09/28. 6 | // Copyright (c) 2014年 BAN Jun. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import NorthLayout 12 | import Ikemen 13 | 14 | class KeyPad : UIView { 15 | let keys: [KanaFlickKey] 16 | var keyButtons: [KeyButton] 17 | 18 | var tapped: ((KanaFlickKey, Int?) -> Void)? 19 | 20 | var metrics: [String:CGFloat] { 21 | return [:] 22 | } 23 | 24 | init(keys: [KanaFlickKey]) { 25 | self.keys = keys 26 | self.keyButtons = [] 27 | super.init(frame: CGRect.zero) 28 | self.addKeypadKeys() 29 | } 30 | 31 | fileprivate func keyButton(_ key: KanaFlickKey) -> KeyButton { 32 | return KeyButton(key: key) ※ { (b:inout KeyButton) in 33 | weak var weakSelf = self 34 | b.tapped = { (key:KanaFlickKey, index:Int?) in 35 | weakSelf?.tapped?(key, index) 36 | return 37 | } 38 | } 39 | } 40 | 41 | func addKeypadKeys() { 42 | if (keys.count != 12) { print("fatal: cannot add keys not having 12 keys to keypad"); return; } 43 | 44 | 45 | let views: [String:UIView] = [ 46 | "a": keyButton(keys[0]), 47 | "b": keyButton(keys[1]), 48 | "c": keyButton(keys[2]), 49 | "d": keyButton(keys[3]), 50 | "e": keyButton(keys[4]), 51 | "f": keyButton(keys[5]), 52 | "g": keyButton(keys[6]), 53 | "h": keyButton(keys[7]), 54 | "i": keyButton(keys[8]), 55 | "j": keyButton(keys[9]), 56 | "k": keyButton(keys[10]), 57 | "l": keyButton(keys[11]), 58 | ] 59 | 60 | let autolayoutInKeyPad = self.northLayoutFormat(metrics, views) 61 | autolayoutInKeyPad("H:|[a][b(==a)][c(==a)]|") 62 | autolayoutInKeyPad("H:|[d(==a)][e(==a)][f(==a)]|") 63 | autolayoutInKeyPad("H:|[g(==a)][h(==a)][i(==a)]|") 64 | autolayoutInKeyPad("H:|[j(==a)][k(==a)][l(==a)]|") 65 | autolayoutInKeyPad("V:|[a][d(==a)][g(==a)][j(==a)]|") 66 | autolayoutInKeyPad("V:|[b(==a)][e(==a)][h(==a)][k(==a)]|") 67 | autolayoutInKeyPad("V:|[c(==a)][f(==a)][i(==a)][l(==a)]|") 68 | 69 | self.keyButtons = (views as NSDictionary).allValues as! [KeyButton] 70 | } 71 | 72 | required init?(coder aDecoder: NSCoder) { 73 | fatalError("init(coder:) has not been implemented") 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /FlickSKKKeyboard/KeyRepeatTimer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeyRepeatTimer.swift 3 | // FlickSKK 4 | // 5 | // Created by MIZUNO Hiroki on 12/13/14. 6 | // Copyright (c) 2014 BAN Jun. All rights reserved. 7 | // 8 | 9 | // KeyRepat用に定期的にコールバックしてくれるタイマ 10 | // 11 | // 1. 起動時にactionを呼び出す 12 | // 2. delayInterval秒後にactionを呼びだす 13 | // 3. repeatInterval秒毎にactionを呼び出す 14 | class KeyRepeatTimer : NSObject { 15 | fileprivate let action : () -> Void 16 | fileprivate let delayInterval : TimeInterval 17 | fileprivate let repeatInterval : TimeInterval 18 | fileprivate var timer : Timer? 19 | 20 | 21 | init(delayInterval : TimeInterval, repeatInterval : TimeInterval, action: @escaping () -> Void) { 22 | self.delayInterval = delayInterval 23 | self.repeatInterval = repeatInterval 24 | self.action = action 25 | } 26 | 27 | func start() { 28 | self.action() 29 | cancel() 30 | self.timer = Timer( 31 | fireAt: Date(timeIntervalSinceNow: self.delayInterval), 32 | interval: self.repeatInterval, 33 | target: self, 34 | selector: #selector(KeyRepeatTimer.repeat), 35 | userInfo: nil, 36 | repeats: true) 37 | RunLoop.current.add(timer!, forMode: .default) 38 | } 39 | 40 | func cancel() { 41 | self.timer?.invalidate() 42 | self.timer = nil 43 | } 44 | 45 | @objc fileprivate func `repeat`() { 46 | self.action() 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /FlickSKKKeyboard/KeyboardImages.xcassets/flickskk-arrow.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "flickskk-arrow.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /FlickSKKKeyboard/KeyboardImages.xcassets/flickskk-arrow.imageset/flickskk-arrow.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/FlickSKKKeyboard/KeyboardImages.xcassets/flickskk-arrow.imageset/flickskk-arrow.pdf -------------------------------------------------------------------------------- /FlickSKKKeyboard/KeyboardImages.xcassets/globe.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "world.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /FlickSKKKeyboard/KeyboardImages.xcassets/globe.imageset/world.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/FlickSKKKeyboard/KeyboardImages.xcassets/globe.imageset/world.pdf -------------------------------------------------------------------------------- /FlickSKKKeyboard/KeyboardMode.swift: -------------------------------------------------------------------------------- 1 | enum KeyboardMode: Hashable { 2 | case inputMode(mode : SKKInputMode) 3 | case number 4 | case alphabet 5 | } 6 | -------------------------------------------------------------------------------- /FlickSKKKeyboard/SKKDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SKKDelegate.swift 3 | // FlickSKK 4 | // 5 | // Created by mzp on 2014/09/29. 6 | // Copyright (c) 2014年 BAN Jun. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol SKKDelegate : AnyObject { 12 | // 確定文字の表示 13 | func insertText(_ text : String) 14 | 15 | // 削除 16 | func deleteBackward() 17 | 18 | // 未確定文字の表示 19 | func composeText(_ text :String?, markedText: String?) 20 | 21 | // 入力モードの変更 22 | func changeInputMode(_ inputMode : SKKInputMode) 23 | 24 | // 変換候補の表示 25 | func showCandidates(_ candidates : [Candidate]?) 26 | } 27 | -------------------------------------------------------------------------------- /FlickSKKKeyboard/SKKEngine.swift: -------------------------------------------------------------------------------- 1 | // SKKのメインエンジン 2 | 3 | class SKKEngine { 4 | fileprivate let keyHandler : KeyHandler 5 | fileprivate weak var delegate : SKKDelegate? 6 | 7 | fileprivate var composeMode : ComposeMode = .directInput 8 | 9 | fileprivate let presenter = ComposeModePresenter() 10 | 11 | init(delegate : SKKDelegate, dictionary : SKKDictionary){ 12 | self.delegate = delegate 13 | self.keyHandler = KeyHandler(delegate: delegate, dictionary: dictionary) 14 | } 15 | 16 | func currentComposeMode() -> ComposeMode { 17 | return self.composeMode 18 | } 19 | 20 | func handle(_ keyEvent : SKKKeyEvent) { 21 | // 状態遷移 22 | composeMode = keyHandler.handle(keyEvent, composeMode: composeMode) 23 | 24 | // 表示を更新 25 | delegate?.composeText(presenter.composeText(composeMode), markedText: presenter.markedText(composeMode)) 26 | 27 | // 候補表示 28 | delegate?.showCandidates(candidates()?.candidates) 29 | } 30 | 31 | func candidates() -> (candidates: [Candidate], index: Int?)? { 32 | return self.presenter.candidates(self.composeMode) 33 | } 34 | 35 | func inStatusShowsCandidatesBySpace() -> Bool { 36 | return self.presenter.inStatusShowsCandidatesBySpace(composeMode) 37 | } 38 | 39 | var isOnInitialStateOfWordRegister: Bool { 40 | presenter.isOnInitialStateOfWordRegister(composeMode) 41 | } 42 | 43 | var hasPartialCandidates: Bool { 44 | let cs = candidates()?.candidates ?? [] 45 | return cs.any{$0.isPartial} 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /FlickSKKKeyboard/SKKInputMode.swift: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // SKKInputMode.swift 4 | // FlickSKK 5 | // 6 | // Created by mzp on 2014/09/30. 7 | // Copyright (c) 2014年 BAN Jun. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | enum SKKInputMode { 12 | case hirakana 13 | case katakana 14 | case hankakuKana 15 | 16 | func kanaType() -> KanaType { 17 | switch self { 18 | case .hirakana: 19 | return .hirakana 20 | case .katakana: 21 | return .katakana 22 | case .hankakuKana: 23 | return .hankakuKana 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /FlickSKKKeyboard/SKKKeyEvent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SKKKey.swift 3 | // FlickSKK 4 | // 5 | // Created by mzp on 2014/09/29. 6 | // Copyright (c) 2014年 BAN Jun. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum SKKKeyEvent { 12 | case char(kana : String, shift : Bool) 13 | case space 14 | case enter 15 | case backspace 16 | case inputModeChange(inputMode : SKKInputMode) 17 | case toggleDakuten(beforeText : String) 18 | case toggleUpperLower(beforeText : String) 19 | case select(index : Int) 20 | case skipPartialCandidates 21 | } 22 | 23 | func ==(l : SKKKeyEvent, r : SKKKeyEvent) -> Bool { 24 | switch (l,r) { 25 | case (.char(kana: let kana1, shift: let shift1), 26 | .char(kana: let kana2, shift: let shift2)): 27 | return kana1 == kana2 && shift1 == shift2 28 | case (.space, .space): 29 | return true 30 | case (.enter, .enter): 31 | return true 32 | case (.backspace, .backspace): 33 | return true 34 | case (.inputModeChange(inputMode: let m1), .inputModeChange(inputMode: let m2)): 35 | return m1 == m2 36 | case (.toggleDakuten(_), .toggleDakuten(_)): 37 | return true 38 | case (.toggleUpperLower(_), .toggleUpperLower(_)): 39 | return true 40 | case (.select(index: let index1), .select(index: let index2)): 41 | return index1 == index2 42 | case (.skipPartialCandidates, .skipPartialCandidates): 43 | return true 44 | default: 45 | return false 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /FlickSKKKeyboard/TextEngine.swift: -------------------------------------------------------------------------------- 1 | // トップレベルと、単語登録モードではテキストの挿入先が異なる。 2 | // そこを抽象化する。 3 | 4 | class TextEngine { 5 | enum Status { 6 | // トップレベルのため、iOS側にテキストの追加・削除を伝える 7 | case topLevel 8 | // 単語登録モード内のため、仮想的なエリアでテキストの追加・削除をする 9 | case compose(text : String, update : (String) -> Void) 10 | } 11 | 12 | fileprivate let dictionary : DictionaryEngine 13 | fileprivate weak var delegate : SKKDelegate? 14 | 15 | init(delegate : SKKDelegate, dictionary : DictionaryEngine) { 16 | self.delegate = delegate 17 | self.dictionary = dictionary 18 | } 19 | 20 | // テキストを確定させる。 learnを指定していれば、変換結果の学習も行なう。 21 | func insertCandidate(_ candidate : Candidate, learn : (String, String?)?, status : Status) -> Status { 22 | learnText(learn, candidate: candidate) 23 | return insertText(text(candidate), status : status) 24 | } 25 | 26 | func insertPartial(_ kanji : String, kana: String, status : Status) -> Status { 27 | dictionary.partial(kana, kanji: kanji) 28 | return insertText(kanji, status: status) 29 | } 30 | 31 | func insert(_ text : String, learn : (String, String?)?, status : Status) -> Status { 32 | return insertCandidate(.exact(kanji: text), learn: learn, status: status) 33 | } 34 | 35 | // 最後の一文字を消す 36 | func deleteBackward(_ status : Status) { 37 | switch status { 38 | case .topLevel: 39 | self.delegate?.deleteBackward() 40 | case .compose(text: let text, update: let update): 41 | let s = text.butLast() 42 | update(s) 43 | } 44 | } 45 | 46 | // 最後と一文字を濁点にする 47 | func toggleDakuten(_ beforeText : String, status : Status) { 48 | switch status { 49 | case .topLevel: 50 | if let s = beforeText.last()?.toggleDakuten() { 51 | self.delegate?.deleteBackward() 52 | self.delegate?.insertText(s) 53 | } 54 | case .compose(text: let text, update: let update): 55 | if let s = text.last()?.toggleDakuten() { 56 | update(text.butLast() + s) 57 | } 58 | } 59 | } 60 | 61 | // 最後と一文字を大文字にする 62 | func toggleUpperLower(_ beforeText : String, status : Status) { 63 | switch status { 64 | case .topLevel: 65 | if let s = beforeText.last()?.toggleUpperLower() { 66 | self.delegate?.deleteBackward() 67 | self.delegate?.insertText(s) 68 | } 69 | case .compose(text: let text, update: let update): 70 | if let s = text.last()?.toggleUpperLower() { 71 | update(text.butLast() + s) 72 | } 73 | } 74 | } 75 | 76 | fileprivate func insertText(_ text : String, status : Status) -> Status { 77 | switch status { 78 | case .topLevel: 79 | self.delegate?.insertText(text) 80 | return .topLevel 81 | case .compose(text: let prev, update: let update): 82 | update(prev + text) 83 | return .compose(text: prev + text, update: update) 84 | } 85 | } 86 | 87 | fileprivate func text(_ candidate : Candidate) -> String { 88 | switch candidate { 89 | case .exact(kanji: let kanji): 90 | return kanji 91 | case .partial(kanji: let kanji, kana: _): 92 | return kanji 93 | } 94 | } 95 | 96 | fileprivate func learnText(_ learn : (String, String?)?, candidate: Candidate) { 97 | if let (kana, okuri) = learn { 98 | func f(_ kana: String, kanji: String) { 99 | if okuri == nil { 100 | self.dictionary.learn(kana, okuri: okuri, kanji: kanji) 101 | } else { 102 | // 送り仮名がある場合、textは送り仮名付きになっている。 103 | // 辞書には送り仮名以外の部分を登録する必要がある。 104 | self.dictionary.learn(kana, okuri: okuri, kanji: kanji.butLast()) 105 | } 106 | } 107 | switch candidate { 108 | case .exact(kanji: let kanji): 109 | f(kana, kanji: kanji) 110 | case .partial(kanji: let kanji, kana: let kana): 111 | f(kana, kanji: kanji) 112 | } 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /FlickSKKTests/BinarySearchSpec.swift: -------------------------------------------------------------------------------- 1 | import Quick 2 | import Nimble 3 | 4 | class BinarySearchSpec : QuickSpec { 5 | override func spec() { 6 | 7 | let entries : NSArray = [ 8 | "Alice", 9 | "Bob", 10 | "Charry", 11 | "David", 12 | "Eve" 13 | ] 14 | 15 | let target = BinarySearch(entries: entries, reverse: false) 16 | 17 | describe("search") { 18 | it("前方マッチ") { 19 | expect(target.call("Cha")).to(equal("Charry")) 20 | } 21 | it("見つからない場合はnil") { 22 | expect(target.call("Charrying")).to(beNil()) 23 | } 24 | } 25 | 26 | describe("境界値") { 27 | it("1要素の場合") { 28 | let target = BinarySearch(entries: ["Alice"], reverse: false) 29 | expect(target.call("Ali")).to(equal("Alice")) 30 | } 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /FlickSKKTests/ComposeModePresenterSpec.swift: -------------------------------------------------------------------------------- 1 | import Quick 2 | import Nimble 3 | 4 | class ComposeModePresenterSpec : QuickSpec { 5 | 6 | override func spec() { 7 | let target = ComposeModePresenter() 8 | let candidates : [Candidate] = [ .exact(kanji: "本気"), .partial(kanji: "マジ", kana: "まじ") ] 9 | describe("toString") { 10 | it("direct input") { 11 | expect(target.composeText(.directInput)).to(beNil()) 12 | expect(target.markedText(.directInput)).to(beNil()) 13 | } 14 | it("kana compose mode") { 15 | expect(target.composeText(.kanaCompose(kana: "こんにちは", candidates: candidates))).to(equal("▽")) 16 | expect(target.markedText(.kanaCompose(kana: "こんにちは", candidates: candidates))).to(equal("こんにちは")) 17 | } 18 | it("kanji compose mode") { 19 | let m = ComposeMode.kanjiCompose(kana: "ほんき", okuri: .none, candidates: candidates, index: 0) 20 | expect(target.composeText(m)).to(equal("▼")) 21 | expect(target.markedText(m)).to(equal("本気")) 22 | } 23 | it("word register mode(direct)") { 24 | let m = ComposeMode.wordRegister(kana: "ほんき", okuri: nil, composeText: "あああ", composeMode: [.directInput]) 25 | expect(target.composeText(m)).to(equal("[登録:ほんき]あああ")) 26 | expect(target.markedText(m)).to(equal("あああ")) 27 | } 28 | it("word register mode(direct)") { 29 | let m = ComposeMode.wordRegister(kana: "ろうたけ", okuri: "る", composeText: "あああ", composeMode: [.directInput]) 30 | expect(target.composeText(m)).to(equal("[登録:ろうたけ*る]あああ")) 31 | expect(target.markedText(m)).to(equal("あああ")) 32 | } 33 | it("word register mode(kana compose)") { 34 | let m = ComposeMode.wordRegister(kana: "ほんき", okuri: nil, composeText: "あ", 35 | composeMode: [.kanaCompose(kana: "い", candidates: [])]) 36 | expect(target.composeText(m)).to(equal("[登録:ほんき]あ▽")) 37 | expect(target.markedText(m)).to(equal("あい")) 38 | } 39 | } 40 | 41 | describe("candidates") { 42 | it("direct input") { 43 | expect(target.candidates(.directInput)).to(beNil()) 44 | } 45 | it("kana compose mode") { 46 | let c = target.candidates(.kanaCompose(kana: "こんにちは", candidates: candidates)) 47 | expect(c?.candidates).to(equal(candidates)) 48 | expect(c?.index).to(beNil()) 49 | 50 | } 51 | it("kanji compose mode") { 52 | let m = ComposeMode.kanjiCompose(kana: "ほんき", okuri: .none, candidates: candidates, index: 1) 53 | let c = target.candidates(m) 54 | expect(c?.candidates).to(equal(candidates)) 55 | expect(c?.index).to(equal(1)) 56 | 57 | } 58 | it("word register mode(direct)") { 59 | let m = ComposeMode.wordRegister(kana: "ほんき", okuri: nil, composeText: "あああ", composeMode: [.directInput]) 60 | expect(target.candidates(m)).to(beNil()) 61 | } 62 | it("word register mode(kana compose)") { 63 | let m = ComposeMode.wordRegister(kana: "ほんき", okuri: nil, composeText: "あ", composeMode: [ 64 | .kanjiCompose(kana: "ほんき", okuri: .none, candidates: candidates, index: 1) 65 | ]) 66 | let (cs, index) = target.candidates(m) ?? ([],0) 67 | expect(cs).to(equal(candidates)) 68 | expect(index).to(equal(1)) 69 | } 70 | } 71 | 72 | 73 | describe("inStatusShowsCandidatesBySpace") { 74 | it("direct input") { 75 | expect(target.inStatusShowsCandidatesBySpace(.directInput)).to(beFalse()) 76 | } 77 | it("kana compose mode") { 78 | let ret = target.inStatusShowsCandidatesBySpace(.kanaCompose(kana: "こんにちは", candidates: candidates)) 79 | expect(ret).to(beTrue()) 80 | } 81 | it("kanji compose mode") { 82 | let m = ComposeMode.kanjiCompose(kana: "ほんき", okuri: .none, candidates: candidates, index: 1) 83 | let ret = target.inStatusShowsCandidatesBySpace(m) 84 | expect(ret).to(beTrue()) 85 | } 86 | it("word register mode(direct)") { 87 | let m = ComposeMode.wordRegister(kana: "ほんき", okuri: nil, composeText: "あああ", composeMode: [.directInput]) 88 | let ret = target.inStatusShowsCandidatesBySpace(m) 89 | expect(ret).to(beFalse()) 90 | } 91 | it("word register mode(kana compose)") { 92 | let m = ComposeMode.wordRegister(kana: "ほんき", okuri: nil, composeText: "あ", composeMode: [ 93 | .kanjiCompose(kana: "ほんき", okuri: .none, candidates: candidates, index: 1) 94 | ]) 95 | let ret = target.inStatusShowsCandidatesBySpace(m) 96 | expect(ret).to(beTrue()) 97 | } 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /FlickSKKTests/DictionaryEngineSpec.swift: -------------------------------------------------------------------------------- 1 | import Quick 2 | import Nimble 3 | 4 | class DictionaryEngineSpec : QuickSpec { 5 | lazy var dictionary : SKKDictionary = { 6 | DictionarySettings.bundle = Bundle(for: self.classForCoder) 7 | let dict = SKKDictionary() 8 | dict.waitForLoading() 9 | return dict 10 | }() 11 | 12 | override func spec() { 13 | var dictionaryEngine : DictionaryEngine! 14 | 15 | beforeEach { 16 | dictionaryEngine = DictionaryEngine(dictionary: self.dictionary) 17 | } 18 | 19 | describe("#find") { 20 | it("送り仮名を補う") { 21 | expect(dictionaryEngine.find("おく", okuri: "る", dynamic: false)).notTo(beEmpty()) 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /FlickSKKTests/EntryParserSpec.swift: -------------------------------------------------------------------------------- 1 | import Quick 2 | import Nimble 3 | 4 | class EntryParserSpec : QuickSpec { 5 | override func spec() { 6 | 7 | describe("正常な形式") { 8 | it("/で分割する") { 9 | let entry = EntryParser(entry: "あお /青/蒼/") 10 | expect(entry.title()).to(equal("あお")) 11 | expect(entry.words()).to(contain("青", "蒼")) 12 | } 13 | it("エスケープを解除する") { 14 | let entry = EntryParser(entry: "あお /[2f]/") 15 | expect(entry.title()).to(equal("あお")) 16 | expect(entry.words()).to(contain("/")) 17 | } 18 | it("アノテーションの除去") { 19 | let entry = EntryParser(entry: "あーがい /アーガイ;魚(ヒブダイ)/") 20 | expect(entry.title()).to(equal("あーがい")) 21 | expect(entry.words()).to(contain("アーガイ")) 22 | } 23 | } 24 | 25 | describe("不正な形式") { 26 | it("見出し語がない") { 27 | let entry = EntryParser(entry: "foo") 28 | expect(entry.title()).to(beNil()) 29 | } 30 | 31 | it("単語が/で囲まれていない") { 32 | let entry = EntryParser(entry: "foo bar") 33 | expect(entry.words()).to(beEmpty()) 34 | } 35 | } 36 | 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /FlickSKKTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.7.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 15 23 | 24 | 25 | -------------------------------------------------------------------------------- /FlickSKKTests/KeyHandlerBaseSpec.swift: -------------------------------------------------------------------------------- 1 | import Quick 2 | import Nimble 3 | 4 | class KeyHandlerBaseSpec : QuickSpec { 5 | lazy var dictionary : SKKDictionary = { 6 | DictionarySettings.bundle = Bundle(for: self.classForCoder) 7 | let dict = SKKDictionary() 8 | dict.waitForLoading() 9 | return dict 10 | }() 11 | 12 | // キーハンドラの取得 13 | func create(_ dictionary : SKKDictionary) -> (KeyHandler, MockDelegate) { 14 | // 学習辞書をリセットする 15 | SKKDictionary.resetLearnDictionary() 16 | 17 | // キーハンドラの生成 18 | let delegate = MockDelegate() 19 | let handler = KeyHandler(delegate: delegate, dictionary: dictionary) 20 | 21 | return (handler, delegate) 22 | } 23 | 24 | func kana(_ composeMode : ComposeMode) -> String? { 25 | switch composeMode { 26 | case .kanaCompose(kana : let kana, candidates: _): 27 | return kana 28 | default: 29 | return nil 30 | } 31 | } 32 | 33 | func kanji(_ composeMode : ComposeMode) -> (String, String)? { 34 | switch composeMode { 35 | case .kanjiCompose(kana: let kana, okuri : let okuri, candidates: _, index: _): 36 | return (kana, okuri ?? "") 37 | default: 38 | return nil 39 | } 40 | } 41 | 42 | func exacts(_ candidates: [String]) -> [Candidate] { 43 | return candidates.map { c in .exact(kanji : c) } 44 | } 45 | 46 | func candidates(_ composeMode : ComposeMode) -> [Candidate]? { 47 | switch composeMode { 48 | case .kanjiCompose(kana: _, okuri: _, candidates: let candidates, index: _): 49 | return candidates 50 | case .kanaCompose(kana: _, candidates: let candidates): 51 | return candidates 52 | default: 53 | return nil 54 | } 55 | } 56 | 57 | func index(_ composeMode: ComposeMode) -> Int? { 58 | switch composeMode { 59 | case .kanjiCompose(kana: _, okuri: _, candidates: _, index: let index): 60 | return index 61 | default: 62 | return nil 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /FlickSKKTests/KeyHandlerDirectInputSpec.swift: -------------------------------------------------------------------------------- 1 | import Quick 2 | import Nimble 3 | 4 | class KeyHandlerDirectInputSpec : KeyHandlerBaseSpec { 5 | 6 | override func spec() { 7 | var handler : KeyHandler! 8 | var delegate : MockDelegate! 9 | 10 | beforeEach { 11 | let (h, d) = self.create(self.dictionary) 12 | handler = h 13 | delegate = d 14 | } 15 | 16 | context("directInput") { 17 | it("文字入力(シフトなし)") { 18 | _ = handler.handle(.char(kana: "あ", shift: false), composeMode: .directInput) 19 | expect(delegate.insertedText).to(equal("あ")) 20 | } 21 | it("Space") { 22 | _ = handler.handle(.space, composeMode: .directInput) 23 | expect(delegate.insertedText).to(equal(" ")) 24 | } 25 | it("Enter") { 26 | _ = handler.handle(.enter, composeMode: .directInput) 27 | expect(delegate.insertedText).to(equal("\n")) 28 | } 29 | it("Backspace") { 30 | delegate.insertedText = "foo" 31 | _ = handler.handle(.backspace, composeMode: .directInput) 32 | expect(delegate.insertedText).to(equal("fo")) 33 | } 34 | it("大文字変換") { 35 | delegate.insertedText = "foo" 36 | _ = handler.handle(.toggleUpperLower(beforeText: "o"), composeMode: .directInput) 37 | expect(delegate.insertedText).to(equal("foO")) 38 | 39 | } 40 | it("濁点変換") { 41 | delegate.insertedText = "か" 42 | _ = handler.handle(.toggleDakuten(beforeText: "か"), composeMode: .directInput) 43 | expect(delegate.insertedText).to(equal("が")) 44 | } 45 | it("入力モード") { 46 | _ = handler.handle(.inputModeChange(inputMode : .katakana), composeMode: .directInput) 47 | expect(delegate.inputMode).to(equal(SKKInputMode.katakana)) 48 | } 49 | it("シフトあり文字入力") { 50 | let m = handler.handle(.char(kana: "あ", shift: true), composeMode: .directInput) 51 | expect(delegate.insertedText).to(equal("")) 52 | expect(self.kana(m)).to(equal("あ")) 53 | expect(self.candidates(m)).toNot(beEmpty()) 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /FlickSKKTests/KeyHandlerWordRegisterWithKanaComposeSpec.swift: -------------------------------------------------------------------------------- 1 | import Quick 2 | import Nimble 3 | 4 | class KeyHandlerWordRegisterWithKanaComposeSpec : KeyHandlerBaseSpec { 5 | 6 | override func spec() { 7 | var handler : KeyHandler! 8 | var delegate : MockDelegate! 9 | 10 | beforeEach { 11 | let (h, d) = self.create(self.dictionary) 12 | handler = h 13 | delegate = d 14 | } 15 | 16 | context("word register with kana mode") { 17 | let composeMode = ComposeMode.wordRegister(kana: "まじ", 18 | okuri: .none, composeText : "か", composeMode: [ .kanaCompose(kana: "あああ", candidates: []) ]) 19 | it("Enter") { 20 | let m = handler.handle(.enter, composeMode : composeMode) 21 | switch m { 22 | case ComposeMode.wordRegister(kana: let k, okuri: let okuri, composeText : let composeText, composeMode : _): 23 | expect(k).to(equal("まじ")) 24 | expect(okuri).to(beNil()) 25 | expect(composeText).to(equal("かあああ")) 26 | default: 27 | fail() 28 | } 29 | } 30 | it("InputModeChange") { 31 | let m = handler.handle(.inputModeChange(inputMode: .katakana), composeMode : composeMode) 32 | switch m { 33 | case ComposeMode.wordRegister(kana: let k, okuri: let okuri, composeText : let composeText, composeMode : _): 34 | expect(k).to(equal("まじ")) 35 | expect(okuri).to(beNil()) 36 | expect(composeText).to(equal("かアアア")) 37 | default: 38 | fail() 39 | } 40 | } 41 | } 42 | 43 | context("word register with kanji mode") { 44 | let composeMode = ComposeMode.wordRegister(kana: "まじ", 45 | okuri: .none, composeText : "か", composeMode: [ .kanjiCompose(kana: "やま", okuri : .none, candidates: self.exacts(["山"]), index: 0)]) 46 | it("Enter") { 47 | let m = handler.handle(.enter, composeMode : composeMode) 48 | switch m { 49 | case ComposeMode.wordRegister(kana: let k, okuri: let okuri, composeText : let composeText, composeMode : _): 50 | expect(k).to(equal("まじ")) 51 | expect(okuri).to(beNil()) 52 | expect(composeText).to(equal("か山")) 53 | // 学習したものが先頭にくる 54 | expect(self.dictionary.find("やま", okuri: nil)[0]).to(equal("山")) 55 | default: 56 | fail() 57 | } 58 | } 59 | it("シフト付き") { 60 | let m = handler.handle(.char(kana: "あ", shift: true), composeMode : composeMode) 61 | switch m { 62 | case ComposeMode.wordRegister(kana: let k, okuri: let okuri, composeText : let composeText, composeMode : let xs): 63 | expect(k).to(equal("まじ")) 64 | expect(okuri).to(beNil()) 65 | expect(composeText).to(equal("か山")) 66 | expect(delegate.insertedText).to(equal("")) 67 | expect(self.kana(xs[0])).to(equal("あ")) 68 | default: 69 | fail() 70 | } 71 | } 72 | it("シフトなし") { 73 | let m = handler.handle(.char(kana: "あ", shift: false), composeMode : composeMode) 74 | switch m { 75 | case ComposeMode.wordRegister(kana: let k, okuri: let okuri, composeText : let composeText, composeMode : let xs): 76 | expect(k).to(equal("まじ")) 77 | expect(okuri).to(beNil()) 78 | expect(composeText).to(equal("か山あ")) 79 | expect(delegate.insertedText).to(equal("")) 80 | expect(xs[0] == .directInput).to(beTrue()) 81 | default: 82 | fail() 83 | } 84 | } 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /FlickSKKTests/LoadLocalDictionarySpec.swift: -------------------------------------------------------------------------------- 1 | import Quick 2 | import Nimble 3 | 4 | class LoadLocalDicitonarySpec : QuickSpec { 5 | override func spec() { 6 | context("正常なファイル") { 7 | let url = DictionarySettings.defaultUserDictionaryURL() 8 | 9 | var dictionary : LoadLocalDictionary! 10 | beforeEach { 11 | if let file = LocalFile(url: url) { 12 | defer { file.close() } 13 | file.clear() 14 | 15 | file.writeln(";; this is dictionary for spec") 16 | file.writeln(";; okuri-ari entries.") 17 | file.writeln("をs /惜/") 18 | file.writeln("われらg /我等/") 19 | file.writeln("") 20 | file.writeln(";; okuri-nasi entries.") 21 | file.writeln("! /!/感嘆符/") 22 | file.writeln("!! /!!/") 23 | } 24 | dictionary = LoadLocalDictionary(url: url) 25 | } 26 | 27 | it("okuri ari") { 28 | expect(dictionary.okuriAri()).to(contain("をs /惜/")) 29 | expect(dictionary.okuriAri()).to(contain("われらg /我等/")) 30 | expect(dictionary.okuriAri()).notTo(contain(";; okuri-ari entries.")) 31 | expect(dictionary.okuriAri()).notTo(contain("")) 32 | } 33 | 34 | it("okuri nasi") { 35 | expect(dictionary.okuriNasi()).to(contain("! /!/感嘆符/")) 36 | expect(dictionary.okuriNasi()).to(contain("!! /!!/")) 37 | expect(dictionary.okuriNasi()).notTo(contain(";; okuri-nasi entries.")) 38 | expect(dictionary.okuriNasi()).notTo(contain("")) 39 | } 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /FlickSKKTests/MockDelegate.swift: -------------------------------------------------------------------------------- 1 | class MockDelegate : SKKDelegate { 2 | var insertedText = "" 3 | var inputMode : SKKInputMode = .hirakana 4 | 5 | func insertText(_ text : String) { 6 | self.insertedText += text 7 | } 8 | 9 | func deleteBackward() { 10 | self.insertedText = self.insertedText.butLast() 11 | } 12 | 13 | func composeText(_ text :String?, markedText: String?) { 14 | } 15 | 16 | func changeInputMode(_ inputMode: SKKInputMode) { 17 | self.inputMode = inputMode 18 | } 19 | 20 | func showCandidates(_ candidates : [Candidate]?) { 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /FlickSKKTests/NumberFliterSpec.swift: -------------------------------------------------------------------------------- 1 | import Quick 2 | import Nimble 3 | 4 | class NumberFilterSpec : QuickSpec { 5 | 6 | func search(_ str : String) -> [String] { 7 | let binarySearch = BinarySearch(entries: [ 8 | "#や /#0夜/#1夜/#2夜/#3夜/" 9 | ], reverse: false) 10 | 11 | return NumberFilter().call(str, binarySearch: binarySearch) { 12 | EntryParser(entry: $0).words() 13 | } 14 | } 15 | 16 | override func spec() { 17 | describe("検索できる") { 18 | it("数字を#に置き換える") { 19 | let target = self.search("15や") 20 | expect(target).notTo(beEmpty()) 21 | } 22 | 23 | it("#nの置換") { 24 | let target = self.search("15や") 25 | expect(target).to(contain("15夜","15夜", "一五夜", "十五夜")) 26 | } 27 | } 28 | describe("32ビット幅を越えた場合") { 29 | it("検索できる") { 30 | let target = self.search("1000000000000や") 31 | expect(target).notTo(beEmpty()) 32 | } 33 | 34 | it("#nの置換") { 35 | let target = self.search("1000000000000や") 36 | expect(target).to(contain("一兆夜")) 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /FlickSKKTests/NumberFormatterSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NumberFormatterSpec.swift 3 | // FlickSKK 4 | // 5 | // Created by MIZUNO Hiroki on 12/27/14. 6 | // Copyright (c) 2014 BAN Jun. All rights reserved. 7 | // 8 | 9 | import Quick 10 | import Nimble 11 | 12 | class NumberFormatterSpec : QuickSpec { 13 | override func spec() { 14 | describe("stringfy") { 15 | it("stringfy int") { 16 | expect(NumberFormatter(value: 0).asAscii()).to(equal("0")) 17 | expect(NumberFormatter(value: 42).asAscii()).to(equal("42")) 18 | } 19 | 20 | it("stringfy as Full width") { 21 | expect(NumberFormatter(value: 0).asFullWidth()).to(equal("0")) 22 | expect(NumberFormatter(value: 42).asFullWidth()).to(equal("42")) 23 | } 24 | 25 | it("stringfy as Japanese") { 26 | expect(NumberFormatter(value: 0).asJapanese()).to(equal("〇")) 27 | expect(NumberFormatter(value: 42).asJapanese()).to(equal("四二")) 28 | } 29 | 30 | 31 | it("stringfy as Kanji") { 32 | expect(NumberFormatter(value: 0).asKanji()).to(equal("零")) 33 | expect(NumberFormatter(value: 42).asKanji()).to(equal("四十二")) 34 | expect(NumberFormatter(value: 1024).asKanji()).to(equal("千二十四")) 35 | expect(NumberFormatter(value: 30000).asKanji()).to(equal("三万")) 36 | expect(NumberFormatter(value: 100000001).asKanji()).to(equal("一億一")) 37 | expect(NumberFormatter(value: 1_0000_0000_0001).asKanji()).to(equal("一兆一")) 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /FlickSKKTests/SKKDictionaryLocalFileSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SKKDictionarySpec.swift 3 | // FlickSKK 4 | // 5 | // Created by mzp on 2014/10/11. 6 | // Copyright (c) 2014年 BAN Jun. All rights reserved. 7 | // 8 | 9 | import Quick 10 | import Nimble 11 | 12 | class SKKDictionaryLocalFileSpec : QuickSpec { 13 | override func spec() { 14 | let bundle = Bundle(for: self.classForCoder) 15 | let jisyo = bundle.url(forResource: "skk", withExtension: "jisyo") 16 | let dict = SKKLocalDictionaryFile(url: jisyo!) 17 | describe("okuri-nasi") { 18 | it("can find at first entry") { 19 | let xs = dict.find("あいかた", okuri: .none) 20 | expect(xs).to(contain("相方")) 21 | expect(xs).to(contain("合方")) 22 | } 23 | it("can find at last entry") { 24 | let xs = dict.find("わりもどし", okuri: .none) 25 | expect(xs).to(contain("割戻し")) 26 | expect(xs).to(contain("割り戻し")) 27 | } 28 | it("can find one word entry") { 29 | let xs = dict.find("じ", okuri: .none) 30 | expect(xs).to(contain("字")) 31 | } 32 | it("can find number entry") { 33 | let xs = dict.find("1えん", okuri: .none) 34 | expect(xs).to(contain("一円")) 35 | expect(xs).to(contain("1円")) 36 | } 37 | 38 | } 39 | describe("okuri-ari") { 40 | it("can find first entry"){ 41 | let xs = dict.find("わりもど", okuri: "s") 42 | expect(xs).to(contain("割り戻")) 43 | expect(xs).to(contain("割戻")) 44 | } 45 | it("can find last entry") { 46 | let xs = dict.find("あいう", okuri: "t") 47 | expect(xs).to(contain("相討")) 48 | expect(xs).to(contain("相打")) 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /FlickSKKTests/SKKDictionarySpec.swift: -------------------------------------------------------------------------------- 1 | import Quick 2 | import Nimble 3 | 4 | class SKKDictionarySpec : QuickSpec { 5 | lazy var dictionary : SKKDictionary = { 6 | DictionarySettings.bundle = Bundle(for: self.classForCoder) 7 | let dict = SKKDictionary() 8 | dict.waitForLoading() 9 | return dict 10 | }() 11 | 12 | override func spec() { 13 | describe("#findDynamic") { 14 | it("重複して取得しない") { 15 | self.dictionary.register("ほんき", okuri: nil, kanji: "本気") 16 | self.dictionary.learn("ほんき", okuri: nil, kanji: "本気") 17 | let xs = self.dictionary.findDynamic("ほん").filter { w in 18 | w.kanji == "本気" 19 | } 20 | expect(xs.count).to(equal(1)) 21 | expect(xs[0].kanji).to(equal("本気")) 22 | expect(xs[0].kana).to(equal("ほんき")) 23 | } 24 | } 25 | 26 | describe("#find") { 27 | it("重複して取得しない") { 28 | self.dictionary.register("ほんき", okuri: nil, kanji: "本気") 29 | let xs = self.dictionary.find("ほんき", okuri: nil).filter { w in 30 | w == "本気" 31 | } 32 | expect(xs.count).to(equal(1)) 33 | } 34 | } 35 | 36 | describe("#partial") { 37 | beforeEach { 38 | self.dictionary.partial("ほんき", okuri: nil, kanji: "ホンキ") 39 | } 40 | 41 | it("ダイナミック変換できる") { 42 | let xs = self.dictionary.findDynamic("ほん") 43 | expect(xs[0].kanji).to(equal("ホンキ")) 44 | } 45 | 46 | it("検索にはでてこない") { 47 | let xs = self.dictionary.find("ほんき", okuri: nil) 48 | expect(xs).toNot(contain("ホンキ")) 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /FlickSKKTests/TextEngineSpec.swift: -------------------------------------------------------------------------------- 1 | import Quick 2 | import Nimble 3 | 4 | class TextEngineSpec : QuickSpec { 5 | lazy var dictionary : SKKDictionary = { 6 | DictionarySettings.bundle = Bundle(for: self.classForCoder) 7 | let dict = SKKDictionary() 8 | dict.waitForLoading() 9 | return dict 10 | }() 11 | 12 | override func spec() { 13 | var target : TextEngine! 14 | var delegate : MockDelegate! 15 | 16 | beforeEach { 17 | delegate = MockDelegate() 18 | let dictionaryEngine = DictionaryEngine(dictionary: self.dictionary) 19 | target = TextEngine(delegate: delegate, dictionary: dictionaryEngine) 20 | } 21 | 22 | describe("#insertPartial") { 23 | beforeEach { 24 | _ = target.insertPartial("ハナヤマタ", kana: "はなやまた", status: TextEngine.Status.topLevel) 25 | } 26 | 27 | it("挿入される") { 28 | expect(delegate.insertedText).to(equal("ハナヤマタ")) 29 | } 30 | 31 | it("補完できる") { 32 | let xs = self.dictionary.findDynamic("はなや").filter { w in w.kanji == "ハナヤマタ" } 33 | expect(xs.count).to(equal(1)) 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /FlickSKKTests/UtilitiesSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UtilitiesSpec.swift 3 | // FlickSKK 4 | // 5 | // Created by mzp on 2014/10/06. 6 | // Copyright (c) 2014年 BAN Jun. All rights reserved. 7 | // 8 | 9 | import Quick 10 | import Nimble 11 | 12 | class UtilitiesSpec: QuickSpec { 13 | override func spec() { 14 | describe("Character") { 15 | context("ひらかな to romain") { 16 | it("convert あ") { 17 | let result = ("あ" as Character).toRoman() 18 | expect(result).to(equal("a")) 19 | } 20 | it("convert が") { 21 | let result = ("が" as Character).toRoman() 22 | expect(result).to(equal("ga")) 23 | } 24 | } 25 | context("カタカナ to romain") { 26 | it("convert ア") { 27 | let result = ("ア" as Character).toRoman() 28 | expect(result).to(equal("a")) 29 | } 30 | it("convert ガ") { 31 | let result = ("ガ" as Character).toRoman() 32 | expect(result).to(equal("ga")) 33 | } 34 | } 35 | context("半角カナ to romain") { 36 | it("convert ア") { 37 | let result = ("ア" as Character).toRoman() 38 | expect(result).to(equal("a")) 39 | } 40 | it("convert ガ") { 41 | let result = ("ガ" as Character).toRoman() 42 | expect(result).to(equal("ga")) 43 | } 44 | } 45 | } 46 | describe("String") { 47 | context("ひらがな to カタカナ") { 48 | it("convert あ行") { 49 | let result = "あいうえお".conv(.hirakana, to: .katakana) 50 | expect(result).to(equal("アイウエオ")) 51 | } 52 | it("convert 'ポップアップ'") { 53 | let result = "ぽっぷあっぷ".conv(.hirakana, to: .katakana) 54 | expect(result).to(equal("ポップアップ")) 55 | } 56 | } 57 | context("ひらがな to ハンカクカナ") { 58 | it("convert あ行") { 59 | let result = "あいうえお".conv(.hirakana, to: .hankakuKana) 60 | expect(result).to(equal("アイウエオ")) 61 | } 62 | it("convert が行") { 63 | let result = "がぎぐげご".conv(.hirakana, to: .hankakuKana) 64 | expect(result).to(equal("ガギグゲゴ")) 65 | } 66 | it("convert ゃゅょー") { 67 | let result = "ゃゅょー".conv(.hirakana, to: .hankakuKana) 68 | expect(result).to(equal("ャュョー")) 69 | } 70 | } 71 | context("toggle upper case/lower case") { 72 | it("convert ABCZ") { 73 | expect("A".toggleUpperLower()).to(equal("a")) 74 | expect("H".toggleUpperLower()).to(equal("h")) 75 | expect("Z".toggleUpperLower()).to(equal("z")) 76 | } 77 | it("convert abc") { 78 | expect("a".toggleUpperLower()).to(equal("A")) 79 | expect("h".toggleUpperLower()).to(equal("H")) 80 | expect("z".toggleUpperLower()).to(equal("Z")) 81 | } 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'cocoapods' 4 | gem 'cocoapods-app_group' 5 | gem 'fastlane' 6 | -------------------------------------------------------------------------------- /Memo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Memo 4 | // 5 | // Created by banjun on 2015/02/15. 6 | // Copyright (c) 2015年 BAN Jun. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | var window: UIWindow? 14 | 15 | func applicationDidFinishLaunching(_ application: UIApplication) { 16 | let vc = ViewController() 17 | let nc = UINavigationController(rootViewController: vc) 18 | self.window = UIWindow(frame: UIScreen.main.bounds) 19 | self.window?.rootViewController = nc 20 | self.window?.makeKeyAndVisible() 21 | } 22 | } 23 | 24 | -------------------------------------------------------------------------------- /Memo/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Memo/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /Memo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.7.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 15 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Memo/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Memo 4 | // 5 | // Created by banjun on 2015/02/15. 6 | // Copyright (c) 2015年 BAN Jun. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | let textView = UITextView() 13 | 14 | override func loadView() { 15 | super.loadView() 16 | 17 | self.textView.backgroundColor = ThemeColor.background 18 | self.textView.autoresizingMask = [UIView.AutoresizingMask.flexibleWidth, UIView.AutoresizingMask.flexibleHeight] 19 | self.textView.font = Appearance.normalFont(18.0) 20 | self.textView.frame = self.view.bounds 21 | self.textView.text = "FlickSKK\n\n日本語入力キーボード FlickSKK。シンプルな日本語入力を、iPhone/iPadで。" 22 | self.view.addSubview(self.textView) 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '13.0' 2 | 3 | use_frameworks! 4 | 5 | target 'FlickSKK' do 6 | pod 'NorthLayout' 7 | pod '※ikemen' 8 | end 9 | 10 | target 'FlickSKKKeyboard' do 11 | pod 'NorthLayout' 12 | pod '※ikemen' 13 | end 14 | 15 | target 'FlickSKKTests' do 16 | pod 'NorthLayout' 17 | pod '※ikemen' 18 | pod 'Quick' 19 | pod 'Nimble' 20 | end 21 | 22 | post_install do |installer| 23 | require 'fileutils' 24 | FileUtils.cp_r( 25 | 'Pods/Target Support Files/Pods-FlickSKK/Pods-FlickSKK-Acknowledgements.plist', 26 | 'FlickSKK/Settings.bundle/Acknowledgements.plist', 27 | remove_destination: true) 28 | 29 | installer.pods_project.targets.each do |t| 30 | t.build_configurations.each do |c| 31 | if Gem::Version.new('12.0') > Gem::Version.new(c.build_settings['IPHONEOS_DEPLOYMENT_TARGET']) 32 | c.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.0' 33 | end 34 | end 35 | end 36 | end 37 | 38 | plugin 'cocoapods-app_group' 39 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - AppGroup (1.0.0) 3 | - FootlessParser (0.5.2) 4 | - Nimble (12.0.0) 5 | - NorthLayout (5.2.0): 6 | - FootlessParser (~> 0.4) 7 | - Quick (6.1.0) 8 | - "※ikemen (0.8.0)" 9 | 10 | DEPENDENCIES: 11 | - AppGroup (from `Pods/CocoaPodsAppGroup/AppGroup.podspec.json`) 12 | - Nimble 13 | - NorthLayout 14 | - Quick 15 | - "※ikemen" 16 | 17 | SPEC REPOS: 18 | trunk: 19 | - FootlessParser 20 | - Nimble 21 | - NorthLayout 22 | - Quick 23 | - "※ikemen" 24 | 25 | EXTERNAL SOURCES: 26 | AppGroup: 27 | :podspec: Pods/CocoaPodsAppGroup/AppGroup.podspec.json 28 | 29 | SPEC CHECKSUMS: 30 | AppGroup: ba0c3626371b5b0f23815f7ae68d14f1061df19f 31 | FootlessParser: 521916d592fa6cba06cfca6f4b5d4f5a2d49175a 32 | Nimble: 02a4990866e51868303a466970cd1c81bb84a192 33 | NorthLayout: 308a58a8f7d96f316e6399ff6432f8769321bbdf 34 | Quick: 6676ffb409bf04abba2ff23902af2407bfc6ac90 35 | "※ikemen": dd846bad2317b0ea51e5c7dd8e565108fa40d528 36 | 37 | PODFILE CHECKSUM: 88486acd6e5763ee4c6b6072f3bca52217dae98a 38 | 39 | COCOAPODS: 1.12.1 40 | -------------------------------------------------------------------------------- /README.mkdn: -------------------------------------------------------------------------------- 1 | # FlickSKK 2 | [AppStoreにて公開しています。](https://itunes.apple.com/jp/app/flickskk/id944678753?mt=8) 3 | 通常利用の場合はAppStore版をご利用ください。 4 | 5 | ## Overview 6 | フリック入力対応版のiOS向けSKKです。 7 | 8 | ## 動作条件 9 | 10 | * iOS8 以降 11 | 12 | ## 主な機能 13 | [AppStoreの説明](https://itunes.apple.com/jp/app/flickskk/id944678753?mt=8) を参照してください。 14 | 15 | ## ビルド方法 16 | 17 | ### ソースコードの取得 18 | ``` 19 | $ git clone https://github.com/codefirst/FlickSKK.git 20 | $ cd FlickSKK 21 | ``` 22 | 23 | ### CocoaPodsの導入 24 | 25 | ``` 26 | $ bundle install 27 | ``` 28 | 29 | ### 設定ファイルの書き換え 30 | 以下のようにAppGroup用のApp IDを指定します。 このApp IDは、自分のDeveloper ProgramのApp IDである必要があります。 31 | 32 | ``` 33 | $ bundle exec pod app-group GROUP_NAME 34 | ``` 35 | 36 | ### ビルド 37 | 38 | ``` 39 | $ bundle exec pod install 40 | ``` 41 | 42 | その後、 FlickSKK.*xcworkspace* を開いてビルドしてください。 (注: FlickSKK.*xcodeproj* ではない) 43 | 44 | ## 辞書の作成 45 | SKK辞書なら利用できます。 ただし、アルファベットのみのエントリ(例: alpha)などは利用しないため取り除くことが望ましいです。 46 | 47 | ``` 48 | ruby ./misc/strip.rb /path/to/skk.jisyo > Resources/skk.jisyo 49 | ``` 50 | 51 | ## License 52 | FlickSKKはSKK-JISYO.Lを元にしたSKK辞書を同梱しています。 53 | 54 | 55 | それ以外の部分についてのライセンスは未定義です。 56 | 57 | ## トラブルシューティング 58 | ### "ld: library not found for -lz" とエラーがでる 59 | 60 | libzがないのが原因です。 適当なlibz.x.x.z.dylib(x.y.zはバージョン番号)へのシンボリックリングを作成すれば回避できます。 61 | 62 | ``` 63 | $ cd /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/lib 64 | $ ln -s libz.x.y.z.dylib libz.dylib 65 | ``` 66 | -------------------------------------------------------------------------------- /ci_scripts/ci_post_clone.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Set the -e flag to stop running the script in case a command returns 4 | # a non-zero exit code. 5 | set -e 6 | set -x 7 | 8 | gem install --user-install bundler 9 | bundle config set --local path .bundle 10 | bundle install 11 | 12 | if [[ ! -e .cocoapods_appgroup ]]; then 13 | bundle exec pod app-group org.codefirst.FlickSKK 14 | fi 15 | 16 | cd .. 17 | bundle exec pod install 18 | -------------------------------------------------------------------------------- /fastlane/Deliverfile: -------------------------------------------------------------------------------- 1 | # The Deliverfile allows you to store various App Store Connect metadata 2 | # For more information, check out the docs 3 | # https://docs.fastlane.tools/actions/deliver/ 4 | skip_binary_upload true 5 | -------------------------------------------------------------------------------- /fastlane/Fastfile: -------------------------------------------------------------------------------- 1 | default_platform(:ios) 2 | 3 | platform :ios do 4 | desc "Push a new beta build to TestFlight" 5 | lane :beta do 6 | build_app(workspace: "FlickSKK.xcworkspace", scheme: "FlickSKK") 7 | upload_to_testflight 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /fastlane/Gymfile: -------------------------------------------------------------------------------- 1 | scheme "FlickSKK" 2 | output_directory "build" 3 | -------------------------------------------------------------------------------- /fastlane/README.md: -------------------------------------------------------------------------------- 1 | fastlane documentation 2 | ================ 3 | # Installation 4 | 5 | Make sure you have the latest version of the Xcode command line tools installed: 6 | 7 | ``` 8 | xcode-select --install 9 | ``` 10 | 11 | Install _fastlane_ using 12 | ``` 13 | [sudo] gem install fastlane -NV 14 | ``` 15 | or alternatively using `brew cask install fastlane` 16 | 17 | # Available Actions 18 | ## iOS 19 | ### ios beta 20 | ``` 21 | fastlane ios beta 22 | ``` 23 | Push a new beta build to TestFlight 24 | 25 | ---- 26 | 27 | This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run. 28 | More information about fastlane can be found on [fastlane.tools](https://fastlane.tools). 29 | The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools). 30 | -------------------------------------------------------------------------------- /fastlane/Scanfile: -------------------------------------------------------------------------------- 1 | scheme "FlickSKK" 2 | -------------------------------------------------------------------------------- /fastlane/metadata/app_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/fastlane/metadata/app_icon.png -------------------------------------------------------------------------------- /fastlane/metadata/copyright.txt: -------------------------------------------------------------------------------- 1 | 2014-2019 codefirst 2 | -------------------------------------------------------------------------------- /fastlane/metadata/ja/apple_tv_privacy_policy.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/metadata/ja/description.txt: -------------------------------------------------------------------------------- 1 | 日本語入力キーボード FlickSKK。シンプルな日本語入力を、iPhone/iPadで。 2 | 3 | ●FlickSKKの特長 4 | ■1. SKKならではのシンプルな日本語変換 5 | ・単文節変換および暗黙の確定による効率的な入力 6 | ・3つの入力モードのサポート: 「ひらかな」モード、「カタカナ」モード、「半角カタカナ」モード 7 | ・ひらかな確定、カタカナ確定、半角カナ確定機能 8 | ・インラインでの単語登録 9 | 10 | 11 | ■2. iPhoneらしいフリック入力 12 | ・フリック入力による自然な入力をサポート 13 | 14 | 15 | ●使い方 16 | ■1. 単文節変換 17 | 「(シフト)」の直後に入力された文字を文節の始まりとして認識します。 18 | ※(シフト) = キーボード右列の上矢印キー 19 | 20 | 例えば「変換」は、以下のように入力できます。 21 | 22 | 1. 「(シフト)」、「へ」、「ん」、「か」、「ん」と押します。 23 | 2. 「次候補」を押して変換候補から入力したい単語を選択します。 24 | 3. 「(確定)」で変換を確定します。 25 | ※(確定) = キーボード右列のリターンキー 26 | 27 | 28 | ■2. 送り仮名変換 29 | 変換中の「(シフト)」は、送り仮名の開始として認識されます。 30 | 31 | 例えば「晴れ」は、以下のように入力できます。 32 | 33 | 1. 「(シフト)」、「は」、「(シフト)」、「れ」と押します。 34 | 2. 「次候補」を押して変換候補から入力したい単語を選択します。 35 | 3. 「(確定)」で変換を確定します。 36 | 37 | 38 | ■3. カタカナ入力 39 | 「かな」を横にフリックすると、カタカナ入力モードになります。 40 | また、変換中に同じ操作をすると、カタカナとして確定されます。 41 | 42 | 同様に、「かな」を上にフリックするとひらかな入力モードに、下にフリックすると半角カナ入力モードになります。 43 | 44 | 45 | ■4. 辞書登録 46 | 辞書に登録されていない単語を変換しようとすると、辞書登録モードになります。 47 | 48 | 変換後の単語を入力し、「(確定)」を押すことで辞書登録ができます。 49 | 50 | 登録した単語は、FlickSKKアプリの「ユーザー辞書」から確認できます。ただし、フルアクセスの許可が必要です。 51 | 52 | ■5. 追加辞書 53 | 外部のSKK辞書を利用できます。 54 | 55 | FlickSKKアプリの「追加辞書」から追加・削除が行なえます。ただし、フルアクセスの許可が必要です。 56 | 57 | ■5. 補完候補 58 | 変換中に2文字以上入力中の場合、変換履歴とユーザー辞書の単語から候補を補完して表示します。 59 | 60 | 補完候補は[次候補]を左フリックしてスキップ[≪]できます。 61 | -------------------------------------------------------------------------------- /fastlane/metadata/ja/keywords.txt: -------------------------------------------------------------------------------- 1 | SKK,Keyboard,キーボード,フリック,日本語入力 2 | -------------------------------------------------------------------------------- /fastlane/metadata/ja/marketing_url.txt: -------------------------------------------------------------------------------- 1 | https://www.codefirst.org/FlickSKK/ 2 | -------------------------------------------------------------------------------- /fastlane/metadata/ja/name.txt: -------------------------------------------------------------------------------- 1 | FlickSKK 2 | -------------------------------------------------------------------------------- /fastlane/metadata/ja/privacy_url.txt: -------------------------------------------------------------------------------- 1 | https://www.codefirst.org/privacy_policy.html 2 | -------------------------------------------------------------------------------- /fastlane/metadata/ja/promotional_text.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/metadata/ja/release_notes.txt: -------------------------------------------------------------------------------- 1 | iOS 13に対応しました 2 | 3 | * 未確定文字列をカーソル位置に表示(マークテキスト) 4 | * ダークモード 5 | -------------------------------------------------------------------------------- /fastlane/metadata/ja/subtitle.txt: -------------------------------------------------------------------------------- 1 | 手のひらにSKKを 2 | -------------------------------------------------------------------------------- /fastlane/metadata/ja/support_url.txt: -------------------------------------------------------------------------------- 1 | https://github.com/codefirst/FlickSKK/issues 2 | -------------------------------------------------------------------------------- /fastlane/metadata/primary_category.txt: -------------------------------------------------------------------------------- 1 | MZGenre.Utilities 2 | -------------------------------------------------------------------------------- /fastlane/metadata/primary_first_sub_category.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/metadata/primary_second_sub_category.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/metadata/secondary_category.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/metadata/secondary_first_sub_category.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/metadata/secondary_second_sub_category.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/screenshots/ja/1_iPad Pro (12.9-inch) (3rd generation).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/fastlane/screenshots/ja/1_iPad Pro (12.9-inch) (3rd generation).png -------------------------------------------------------------------------------- /fastlane/screenshots/ja/1_iPhone Xs Max.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/fastlane/screenshots/ja/1_iPhone Xs Max.png -------------------------------------------------------------------------------- /fastlane/screenshots/ja/1_ipadPro_1.Simulator Screen Shot Aug 30, 2016, 23.21.39.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/fastlane/screenshots/ja/1_ipadPro_1.Simulator Screen Shot Aug 30, 2016, 23.21.39.png -------------------------------------------------------------------------------- /fastlane/screenshots/ja/1_ipad_1.iPad-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/fastlane/screenshots/ja/1_ipad_1.iPad-1.png -------------------------------------------------------------------------------- /fastlane/screenshots/ja/1_iphone35_1.iPhone-3.5inch-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/fastlane/screenshots/ja/1_iphone35_1.iPhone-3.5inch-1.png -------------------------------------------------------------------------------- /fastlane/screenshots/ja/1_iphone4_1.iPhone-4.0inch-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/fastlane/screenshots/ja/1_iphone4_1.iPhone-4.0inch-1.png -------------------------------------------------------------------------------- /fastlane/screenshots/ja/1_iphone6Plus_1.iPhone-5.5inch-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/fastlane/screenshots/ja/1_iphone6Plus_1.iPhone-5.5inch-1.png -------------------------------------------------------------------------------- /fastlane/screenshots/ja/1_iphone6_1.iPhone-4.7inch-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/fastlane/screenshots/ja/1_iphone6_1.iPhone-4.7inch-1.png -------------------------------------------------------------------------------- /fastlane/screenshots/ja/2_iPad Pro (12.9-inch) (3rd generation).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/fastlane/screenshots/ja/2_iPad Pro (12.9-inch) (3rd generation).png -------------------------------------------------------------------------------- /fastlane/screenshots/ja/2_iPhone Xs Max.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/fastlane/screenshots/ja/2_iPhone Xs Max.png -------------------------------------------------------------------------------- /fastlane/screenshots/ja/2_ipadPro_2.Simulator Screen Shot Aug 30, 2016, 23.21.57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/fastlane/screenshots/ja/2_ipadPro_2.Simulator Screen Shot Aug 30, 2016, 23.21.57.png -------------------------------------------------------------------------------- /fastlane/screenshots/ja/2_ipad_2.iPad-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/fastlane/screenshots/ja/2_ipad_2.iPad-2.png -------------------------------------------------------------------------------- /fastlane/screenshots/ja/2_iphone35_2.iPhone-3.5inch-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/fastlane/screenshots/ja/2_iphone35_2.iPhone-3.5inch-2.png -------------------------------------------------------------------------------- /fastlane/screenshots/ja/2_iphone4_2.iPhone-4.0inch-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/fastlane/screenshots/ja/2_iphone4_2.iPhone-4.0inch-2.png -------------------------------------------------------------------------------- /fastlane/screenshots/ja/2_iphone6Plus_2.iPhone-5.5inch-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/fastlane/screenshots/ja/2_iphone6Plus_2.iPhone-5.5inch-2.png -------------------------------------------------------------------------------- /fastlane/screenshots/ja/2_iphone6_2.iPhone-4.7inch-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/fastlane/screenshots/ja/2_iphone6_2.iPhone-4.7inch-2.png -------------------------------------------------------------------------------- /fastlane/screenshots/ja/3_iPad Pro (12.9-inch) (3rd generation).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/fastlane/screenshots/ja/3_iPad Pro (12.9-inch) (3rd generation).png -------------------------------------------------------------------------------- /fastlane/screenshots/ja/3_iPhone Xs Max.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/fastlane/screenshots/ja/3_iPhone Xs Max.png -------------------------------------------------------------------------------- /fastlane/screenshots/ja/3_ipadPro_3.Simulator Screen Shot Aug 30, 2016, 23.22.18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/fastlane/screenshots/ja/3_ipadPro_3.Simulator Screen Shot Aug 30, 2016, 23.22.18.png -------------------------------------------------------------------------------- /fastlane/screenshots/ja/3_ipad_3.iPad-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/fastlane/screenshots/ja/3_ipad_3.iPad-3.png -------------------------------------------------------------------------------- /fastlane/screenshots/ja/3_iphone35_3.iPhone-3.5inch-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/fastlane/screenshots/ja/3_iphone35_3.iPhone-3.5inch-3.png -------------------------------------------------------------------------------- /fastlane/screenshots/ja/3_iphone4_3.iPhone-4.0inch-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/fastlane/screenshots/ja/3_iphone4_3.iPhone-4.0inch-3.png -------------------------------------------------------------------------------- /fastlane/screenshots/ja/3_iphone6Plus_3.iPhone-5.5inch-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/fastlane/screenshots/ja/3_iphone6Plus_3.iPhone-5.5inch-3.png -------------------------------------------------------------------------------- /fastlane/screenshots/ja/3_iphone6_3.iPhone-4.7inch-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/fastlane/screenshots/ja/3_iphone6_3.iPhone-4.7inch-3.png -------------------------------------------------------------------------------- /fastlane/screenshots/ja/4_iPad Pro (12.9-inch) (3rd generation).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/fastlane/screenshots/ja/4_iPad Pro (12.9-inch) (3rd generation).png -------------------------------------------------------------------------------- /fastlane/screenshots/ja/4_iPhone Xs Max.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/fastlane/screenshots/ja/4_iPhone Xs Max.png -------------------------------------------------------------------------------- /fastlane/screenshots/ja/4_ipadPro_4.Simulator Screen Shot Aug 30, 2016, 23.27.24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/fastlane/screenshots/ja/4_ipadPro_4.Simulator Screen Shot Aug 30, 2016, 23.27.24.png -------------------------------------------------------------------------------- /fastlane/screenshots/ja/4_ipad_4.iPad-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/fastlane/screenshots/ja/4_ipad_4.iPad-4.png -------------------------------------------------------------------------------- /fastlane/screenshots/ja/4_iphone35_4.iPhone-3.5inch-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/fastlane/screenshots/ja/4_iphone35_4.iPhone-3.5inch-4.png -------------------------------------------------------------------------------- /fastlane/screenshots/ja/4_iphone4_4.iPhone-4.0inch-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/fastlane/screenshots/ja/4_iphone4_4.iPhone-4.0inch-4.png -------------------------------------------------------------------------------- /fastlane/screenshots/ja/4_iphone6Plus_4.iPhone-5.5inch-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/fastlane/screenshots/ja/4_iphone6Plus_4.iPhone-5.5inch-4.png -------------------------------------------------------------------------------- /fastlane/screenshots/ja/4_iphone6_4.iPhone-4.7inch-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefirst/FlickSKK/e07ff391d5bf3c0f7868784273d5854eeb539adf/fastlane/screenshots/ja/4_iphone6_4.iPhone-4.7inch-4.png -------------------------------------------------------------------------------- /misc/screenshot.rb: -------------------------------------------------------------------------------- 1 | # usage: 2 | # cd iTunesConnect/FlickSKK.itmsp 3 | # ruby ../../misc/screenshot.rb *3.5* *iPad* *4.0* *5.5* *4.7* > ~/tmp/screenshots.txt 4 | TARGET = { 5 | 'iPad' => 'iOS-iPad', 6 | 'iPhone-3.5inch' => 'iOS-3.5-in', 7 | 'iPhone-4.0inch' => 'iOS-4-in', 8 | 'iPhone-4.7inch' => 'iOS-4.7-in', 9 | 'iPhone-5.5inch' => 'iOS-5.5-in' 10 | } 11 | 12 | def target(name) 13 | TARGET.each do |key, value| 14 | if name =~ /#{key}/ 15 | return value 16 | end 17 | end 18 | end 19 | 20 | def position(name) 21 | if name =~ /-(\d+)\.png/ 22 | $1 23 | end 24 | end 25 | 26 | ARGV.each do |file| 27 | filename = File.basename file 28 | md5 = `md5 -q #{file}`.strip 29 | puts <<-XML 30 | #{" "*5} 31 | #{" "*6}#{File.size file} 32 | #{" "*6}#{filename} 33 | #{" "*6}#{md5} 34 | #{" "*5} 35 | XML 36 | end 37 | -------------------------------------------------------------------------------- /misc/sort.rb: -------------------------------------------------------------------------------- 1 | xs = ARGF.readlines 2 | 3 | header = [] 4 | ari = [] 5 | nasi = [] 6 | status = :header 7 | 8 | xs.each do|x| 9 | case x 10 | when /;; okuri-ari entries/ 11 | status = :ari 12 | next 13 | when /;; okuri-nasi entries/ 14 | status = :nasi 15 | next 16 | end 17 | 18 | case status 19 | when :header 20 | header << x 21 | when :ari 22 | ari << x 23 | when :nasi 24 | nasi << x 25 | end 26 | end 27 | 28 | def print_each(xs) 29 | xs.each {|x| print x} 30 | end 31 | 32 | print_each header 33 | puts ";; okuri-ari entries." 34 | print_each ari.sort.reverse 35 | puts ";; okuri-nasi entries." 36 | print_each nasi.sort 37 | -------------------------------------------------------------------------------- /misc/strip.rb: -------------------------------------------------------------------------------- 1 | ARGF.each do|line| 2 | entry,_ = line.split ' ',2 3 | case 4 | when entry =~ /\A;/ 5 | # 著作権表示等を残すためコメント行は削除しない 6 | print line 7 | when !entry.ascii_only? 8 | print line 9 | end 10 | end 11 | --------------------------------------------------------------------------------