├── .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 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
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 | 「⇧」、「へ」、「ん」、「か」、「ん」と押します。
17 | 「次候補」を押して変換候補から入力したい単語を選択します。
18 | 「⏎」で変換を確定します。
19 |
20 |
21 | 2. 送り仮名変換
22 | 変換中の「⇧」は、送り仮名の開始として認識されます。
23 | 例えば「晴れ」は、以下のように入力できます。
24 |
25 |
26 | 「⇧」、「は」、「⇧」、「れ」と押します。
27 | 「次候補」を押して変換候補から入力したい単語を選択します。
28 | 「⏎」で変換を確定します。
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 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
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 |
--------------------------------------------------------------------------------