diff --git a/.clang-format-swift.json b/.clang-format-swift.json index 2ebfa011..66d5b897 100644 --- a/.clang-format-swift.json +++ b/.clang-format-swift.json @@ -3,7 +3,7 @@ "accessLevel" : "private" }, "indentation" : { - "tabs" : 1 + "spaces" : 2 }, "indentConditionalCompilationBlocks" : true, "indentSwitchCaseLabels" : true, @@ -51,6 +51,6 @@ "UseWhereClausesInForLoops" : false, "ValidateDocumentationComments" : false }, - "tabWidth" : 4, + "tabWidth" : 8, "version" : 1 } diff --git a/BuildVersionSpecifier.swift b/BuildVersionSpecifier.swift index 691bd3ab..1f068bfc 100755 --- a/BuildVersionSpecifier.swift +++ b/BuildVersionSpecifier.swift @@ -27,18 +27,18 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import Cocoa extension String { - fileprivate mutating func regReplace(pattern: String, replaceWith: String = "") { - // Ref: https://stackoverflow.com/a/40993403/4162914 && https://stackoverflow.com/a/71291137/4162914 - do { - let regex = try NSRegularExpression( - pattern: pattern, options: [.caseInsensitive, .anchorsMatchLines] - ) - let range = NSRange(startIndex..., in: self) - self = regex.stringByReplacingMatches( - in: self, options: [], range: range, withTemplate: replaceWith - ) - } catch { return } - } + fileprivate mutating func regReplace(pattern: String, replaceWith: String = "") { + // Ref: https://stackoverflow.com/a/40993403/4162914 && https://stackoverflow.com/a/71291137/4162914 + do { + let regex = try NSRegularExpression( + pattern: pattern, options: [.caseInsensitive, .anchorsMatchLines] + ) + let range = NSRange(startIndex..., in: self) + self = regex.stringByReplacingMatches( + in: self, options: [], range: range, withTemplate: replaceWith + ) + } catch { return } + } } var verMarket: String = "1.0.0" @@ -50,39 +50,39 @@ var dirUpdateInfoPlist = "./Update-Info.plist" var theDictionary: NSDictionary? if CommandLine.arguments.count == 3 { - verMarket = CommandLine.arguments[1] - verBuild = CommandLine.arguments[2] + verMarket = CommandLine.arguments[1] + verBuild = CommandLine.arguments[2] - // Xcode project file version update. - do { - strXcodeProjContent += try String(contentsOfFile: dirXcodeProjectFile, encoding: .utf8) - } catch { - NSLog(" - Exception happened when reading raw phrases data.") - } + // Xcode project file version update. + do { + strXcodeProjContent += try String(contentsOfFile: dirXcodeProjectFile, encoding: .utf8) + } catch { + NSLog(" - Exception happened when reading raw phrases data.") + } - strXcodeProjContent.regReplace( - pattern: #"CURRENT_PROJECT_VERSION = .*$"#, replaceWith: "CURRENT_PROJECT_VERSION = " + verBuild + ";" - ) - strXcodeProjContent.regReplace( - pattern: #"MARKETING_VERSION = .*$"#, replaceWith: "MARKETING_VERSION = " + verMarket + ";" - ) - do { - try strXcodeProjContent.write(to: URL(fileURLWithPath: dirXcodeProjectFile), atomically: false, encoding: .utf8) - } catch { - NSLog(" -: Error on writing strings to file: \(error)") - } - NSLog(" - Xcode 專案版本資訊更新完成:\(verMarket) \(verBuild)。") + strXcodeProjContent.regReplace( + pattern: #"CURRENT_PROJECT_VERSION = .*$"#, replaceWith: "CURRENT_PROJECT_VERSION = " + verBuild + ";" + ) + strXcodeProjContent.regReplace( + pattern: #"MARKETING_VERSION = .*$"#, replaceWith: "MARKETING_VERSION = " + verMarket + ";" + ) + do { + try strXcodeProjContent.write(to: URL(fileURLWithPath: dirXcodeProjectFile), atomically: false, encoding: .utf8) + } catch { + NSLog(" -: Error on writing strings to file: \(error)") + } + NSLog(" - Xcode 專案版本資訊更新完成:\(verMarket) \(verBuild)。") - // Packages project file version update. - theDictionary = NSDictionary(contentsOfFile: dirPackageProjectFile) - theDictionary?.setValue(verMarket, forKeyPath: "PACKAGES.PACKAGE_SETTINGS.VERSION") - theDictionary?.write(toFile: dirPackageProjectFile, atomically: true) - NSLog(" - Packages 專案版本資訊更新完成:\(verMarket) \(verBuild)。") + // Packages project file version update. + theDictionary = NSDictionary(contentsOfFile: dirPackageProjectFile) + theDictionary?.setValue(verMarket, forKeyPath: "PACKAGES.PACKAGE_SETTINGS.VERSION") + theDictionary?.write(toFile: dirPackageProjectFile, atomically: true) + NSLog(" - Packages 專案版本資訊更新完成:\(verMarket) \(verBuild)。") - // Update notification project file version update. - theDictionary = NSDictionary(contentsOfFile: dirUpdateInfoPlist) - theDictionary?.setValue(verBuild, forKeyPath: "CFBundleVersion") - theDictionary?.setValue(verMarket, forKeyPath: "CFBundleShortVersionString") - theDictionary?.write(toFile: dirUpdateInfoPlist, atomically: true) - NSLog(" - 更新用通知 plist 版本資訊更新完成:\(verMarket) \(verBuild)。") + // Update notification project file version update. + theDictionary = NSDictionary(contentsOfFile: dirUpdateInfoPlist) + theDictionary?.setValue(verBuild, forKeyPath: "CFBundleVersion") + theDictionary?.setValue(verMarket, forKeyPath: "CFBundleShortVersionString") + theDictionary?.write(toFile: dirUpdateInfoPlist, atomically: true) + NSLog(" - 更新用通知 plist 版本資訊更新完成:\(verMarket) \(verBuild)。") } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3c39d821..135d82e7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,14 +1,14 @@ # 威注音輸入法研發參與相關說明 -威注音輸入法歡迎有人參與。但為了不讓參與者們浪費各自的熱情,特設此文以說明該專案目前最需要協助的地方。 +威注音輸入法歡迎有熱心的志願者們參與。 -1. 有人能用 Swift 將該專案內這兩個源自 LibFormosa 的組件套件重寫: +威注音目前的 codebase 更能代表一個先進的 macOS 輸入法雛形專案的形態。目前的 dev 分支除了 Mandarin 模組(以及其與 KeyHandler 的對接的部分)以外被威注音使用的部分全都是清一色的 Swift codebase,一目了然,方便他人參與,比某些其它開源品牌旗下的專案更具程式方面的生命力。為什麼這樣講呢?那些傳統開源品牌的專案主要使用 C++ 這門不太友好的語言(Mandarin 模組現在對我而言仍舊是天書,一大堆針對記憶體指針的操作完全看不懂。搞不清楚在這一層之上的功能邏輯的話,就無法制定 Swift 版的 coding 策略),這也是我這次用 Swift 重寫了語言模型引擎的原因(也是為後來者行方便)。 + +為了不讓參與者們浪費各自的熱情,特設此文以說明該專案目前最需要協助的地方。 + +1. 有人能用 Swift 將該專案內的這個源自 LibFormosa 的組件套件重寫: - Mandarin 組件,用以分析普通話音韻數據、創建且控制 Syllable Composer 注音拼識組件。 - - Gramambular 套裝,這包括了 Source 資料夾下的其餘全部的 (Obj)C(++) 檔案(LMConsolidator 除外)。 - - LMConsolidator 有 Swift 版本,已經用於威注音語彙編輯器內。給主程式用 C++ 版本僅為了與 Gramambular 協作方便。 - - 這也包括了所有與 Language Model 有關的實現,因為都是 Gramambular 內的某個語言模組 Protocol 衍生出來的東西。 - - LMInstantiator 是用來將語言模組副本化的組件,原本不屬於 Gramambular,但與其衍生的各類語言模組高度耦合。 - - KeyValueBlobReader 不屬於 Gramambular,但與其衍生的各類語言模組高度耦合、也與 KeyHandler 高度耦合。 + - 一堆記憶體指針操作,實在看不懂這個組件的處理邏輯是什麼,無能為力。 2. 讓 Alt+波浪鍵選單能夠在諸如 MS Word 以及終端機內正常工作(可以用方向鍵控制高亮候選內容,等)。 - 原理上而言恐怕得欺騙當前正在接受輸入的應用、使其誤以為當前有組字區。這只是推測。 3. SQLite 實現。 @@ -25,11 +25,9 @@ 該專案對源碼格式有規範,且 Swift 與其他 (Obj)C(++) 系語言持不同規範: - Swift: 採 [Apple 官方 Swift-Format](https://github.com/apple/swift-format),且施加如下例外修改項目: - - Indentation 僅使用 `"indentation" : { "tabs" : 1 },`,不以空格來縮進。 - `"indentSwitchCaseLabels" : true,` - `"lineLength" : 120,` - `"NoBlockComments" : false,` - - `"tabWidth" : 4,` - `"OnlyOneTrailingClosureArgument" : false,` // SwiftUI 相容 - `"UseTripleSlashForDocumentationComments" : false,` - `"DontRepeatTypeInStaticProperties" : false,` @@ -37,6 +35,6 @@ - 該規範以四個西文半形空格為行縮進單位。 - 由於今後不會再用這類語言給該倉庫新增內容,所以相關規範就不改動了。 -至於對 Swift 檔案改採 1-Tab 縮進,則是為了在尊重所有用戶的需求的同時、最大程度上節約檔案體積。使用者可自行修改 Xcode 的預設 Tab 縮進尺寸。 +之前,為了節省檔案體積,曾經對 Swift 檔案改採 1-Tab 縮進。然而,這會導致 Gitee 等線上 git 專案管理網站內的顯示變成 8-Space 縮進。於是,該專案對 Swift 檔案又改回了 2-Spaces 縮進。 -$ EOF. \ No newline at end of file +$ EOF. diff --git a/DataCompiler/dataCompiler.swift b/DataCompiler/dataCompiler.swift index 8448ba6f..9122721d 100644 --- a/DataCompiler/dataCompiler.swift +++ b/DataCompiler/dataCompiler.swift @@ -29,55 +29,55 @@ import Foundation // MARK: - 前導工作 extension String { - fileprivate mutating func regReplace(pattern: String, replaceWith: String = "") { - // Ref: https://stackoverflow.com/a/40993403/4162914 && https://stackoverflow.com/a/71291137/4162914 - do { - let regex = try NSRegularExpression( - pattern: pattern, options: [.caseInsensitive, .anchorsMatchLines] - ) - let range = NSRange(startIndex..., in: self) - self = regex.stringByReplacingMatches( - in: self, options: [], range: range, withTemplate: replaceWith - ) - } catch { return } - } + fileprivate mutating func regReplace(pattern: String, replaceWith: String = "") { + // Ref: https://stackoverflow.com/a/40993403/4162914 && https://stackoverflow.com/a/71291137/4162914 + do { + let regex = try NSRegularExpression( + pattern: pattern, options: [.caseInsensitive, .anchorsMatchLines] + ) + let range = NSRange(startIndex..., in: self) + self = regex.stringByReplacingMatches( + in: self, options: [], range: range, withTemplate: replaceWith + ) + } catch { return } + } } // MARK: - 引入小數點位數控制函數 // Ref: https://stackoverflow.com/a/32581409/4162914 extension Float { - fileprivate func rounded(toPlaces places: Int) -> Float { - let divisor = pow(10.0, Float(places)) - return (self * divisor).rounded() / divisor - } + fileprivate func rounded(toPlaces places: Int) -> Float { + let divisor = pow(10.0, Float(places)) + return (self * divisor).rounded() / divisor + } } // MARK: - 引入幂乘函數 // Ref: https://stackoverflow.com/a/41581695/4162914 precedencegroup ExponentiationPrecedence { - associativity: right - higherThan: MultiplicationPrecedence + associativity: right + higherThan: MultiplicationPrecedence } infix operator **: ExponentiationPrecedence func ** (_ base: Double, _ exp: Double) -> Double { - pow(base, exp) + pow(base, exp) } func ** (_ base: Float, _ exp: Float) -> Float { - pow(base, exp) + pow(base, exp) } // MARK: - 定義檔案結構 struct Entry { - var valPhone: String = "" - var valPhrase: String = "" - var valWeight: Float = -1.0 - var valCount: Int = 0 + var valPhone: String = "" + var valPhrase: String = "" + var valWeight: Float = -1.0 + var valCount: Int = 0 } // MARK: - 登記全局根常數變數 @@ -105,322 +105,322 @@ private let urlOutputCHT: String = "./data-cht.txt" // MARK: - 載入詞組檔案且輸出陣列 func rawDictForPhrases(isCHS: Bool) -> [Entry] { - var arrEntryRAW: [Entry] = [] - var strRAW = "" - let urlCustom: String = isCHS ? urlCHSforCustom : urlCHTforCustom - let urlMCBP: String = isCHS ? urlCHSforMCBP : urlCHTforMCBP - let urlMOE: String = isCHS ? urlCHSforMOE : urlCHTforMOE - let urlVCHEW: String = isCHS ? urlCHSforVCHEW : urlCHTforVCHEW - let i18n: String = isCHS ? "簡體中文" : "繁體中文" - // 讀取內容 - do { - strRAW += try String(contentsOfFile: urlCustom, encoding: .utf8) - strRAW += "\n" - strRAW += try String(contentsOfFile: urlMCBP, encoding: .utf8) - strRAW += "\n" - strRAW += try String(contentsOfFile: urlMOE, encoding: .utf8) - strRAW += "\n" - strRAW += try String(contentsOfFile: urlVCHEW, encoding: .utf8) - } catch { - NSLog(" - Exception happened when reading raw phrases data.") - return [] - } - // 預處理格式 - strRAW = strRAW.replacingOccurrences(of: " #MACOS", with: "") // 去掉 macOS 標記 - // CJKWhiteSpace (\x{3000}) to ASCII Space - // NonBreakWhiteSpace (\x{A0}) to ASCII Space - // Tab to ASCII Space - // 統整連續空格為一個 ASCII 空格 - strRAW.regReplace(pattern: #"( +| +| +|\t+)+"#, replaceWith: " ") - strRAW.regReplace(pattern: #"(^ | $)"#, replaceWith: "") // 去除行尾行首空格 - strRAW.regReplace(pattern: #"(\f+|\r+|\n+)+"#, replaceWith: "\n") // CR & Form Feed to LF, 且去除重複行 - strRAW.regReplace(pattern: #"^(#.*|.*#WIN32.*)$"#, replaceWith: "") // 以#開頭的行都淨空+去掉所有 WIN32 特有的行 - // 正式整理格式,現在就開始去重複: - let arrData = Array( - NSOrderedSet(array: strRAW.components(separatedBy: "\n")).array as! [String]) - for lineData in arrData { - // 第三欄開始是注音 - let arrLineData = lineData.components(separatedBy: " ") - var varLineDataProcessed = "" - var count = 0 - for currentCell in arrLineData { - count += 1 - if count < 3 { - varLineDataProcessed += currentCell + "\t" - } else if count < arrLineData.count { - varLineDataProcessed += currentCell + "-" - } else { - varLineDataProcessed += currentCell - } - } - // 然後直接乾脆就轉成 Entry 吧。 - let arrCells: [String] = varLineDataProcessed.components(separatedBy: "\t") - count = 0 // 不需要再定義,因為之前已經有定義過了。 - var phone = "" - var phrase = "" - var occurrence = 0 - for cell in arrCells { - count += 1 - switch count { - case 1: phrase = cell - case 3: phone = cell - case 2: occurrence = Int(cell) ?? 0 - default: break - } - } - if phrase != "" { // 廢掉空數據;之後無須再這樣處理。 - arrEntryRAW += [ - Entry( - valPhone: phone, valPhrase: phrase, valWeight: 0.0, - valCount: occurrence - ) - ] - } - } - NSLog(" - \(i18n): 成功生成詞語語料辭典(權重待計算)。") - return arrEntryRAW + var arrEntryRAW: [Entry] = [] + var strRAW = "" + let urlCustom: String = isCHS ? urlCHSforCustom : urlCHTforCustom + let urlMCBP: String = isCHS ? urlCHSforMCBP : urlCHTforMCBP + let urlMOE: String = isCHS ? urlCHSforMOE : urlCHTforMOE + let urlVCHEW: String = isCHS ? urlCHSforVCHEW : urlCHTforVCHEW + let i18n: String = isCHS ? "簡體中文" : "繁體中文" + // 讀取內容 + do { + strRAW += try String(contentsOfFile: urlCustom, encoding: .utf8) + strRAW += "\n" + strRAW += try String(contentsOfFile: urlMCBP, encoding: .utf8) + strRAW += "\n" + strRAW += try String(contentsOfFile: urlMOE, encoding: .utf8) + strRAW += "\n" + strRAW += try String(contentsOfFile: urlVCHEW, encoding: .utf8) + } catch { + NSLog(" - Exception happened when reading raw phrases data.") + return [] + } + // 預處理格式 + strRAW = strRAW.replacingOccurrences(of: " #MACOS", with: "") // 去掉 macOS 標記 + // CJKWhiteSpace (\x{3000}) to ASCII Space + // NonBreakWhiteSpace (\x{A0}) to ASCII Space + // Tab to ASCII Space + // 統整連續空格為一個 ASCII 空格 + strRAW.regReplace(pattern: #"( +| +| +|\t+)+"#, replaceWith: " ") + strRAW.regReplace(pattern: #"(^ | $)"#, replaceWith: "") // 去除行尾行首空格 + strRAW.regReplace(pattern: #"(\f+|\r+|\n+)+"#, replaceWith: "\n") // CR & Form Feed to LF, 且去除重複行 + strRAW.regReplace(pattern: #"^(#.*|.*#WIN32.*)$"#, replaceWith: "") // 以#開頭的行都淨空+去掉所有 WIN32 特有的行 + // 正式整理格式,現在就開始去重複: + let arrData = Array( + NSOrderedSet(array: strRAW.components(separatedBy: "\n")).array as! [String]) + for lineData in arrData { + // 第三欄開始是注音 + let arrLineData = lineData.components(separatedBy: " ") + var varLineDataProcessed = "" + var count = 0 + for currentCell in arrLineData { + count += 1 + if count < 3 { + varLineDataProcessed += currentCell + "\t" + } else if count < arrLineData.count { + varLineDataProcessed += currentCell + "-" + } else { + varLineDataProcessed += currentCell + } + } + // 然後直接乾脆就轉成 Entry 吧。 + let arrCells: [String] = varLineDataProcessed.components(separatedBy: "\t") + count = 0 // 不需要再定義,因為之前已經有定義過了。 + var phone = "" + var phrase = "" + var occurrence = 0 + for cell in arrCells { + count += 1 + switch count { + case 1: phrase = cell + case 3: phone = cell + case 2: occurrence = Int(cell) ?? 0 + default: break + } + } + if phrase != "" { // 廢掉空數據;之後無須再這樣處理。 + arrEntryRAW += [ + Entry( + valPhone: phone, valPhrase: phrase, valWeight: 0.0, + valCount: occurrence + ) + ] + } + } + NSLog(" - \(i18n): 成功生成詞語語料辭典(權重待計算)。") + return arrEntryRAW } // MARK: - 載入單字檔案且輸出陣列 func rawDictForKanjis(isCHS: Bool) -> [Entry] { - var arrEntryRAW: [Entry] = [] - var strRAW = "" - let i18n: String = isCHS ? "簡體中文" : "繁體中文" - // 讀取內容 - do { - strRAW += try String(contentsOfFile: urlKanjiCore, encoding: .utf8) - } catch { - NSLog(" - Exception happened when reading raw core kanji data.") - return [] - } - // 預處理格式 - strRAW = strRAW.replacingOccurrences(of: " #MACOS", with: "") // 去掉 macOS 標記 - // CJKWhiteSpace (\x{3000}) to ASCII Space - // NonBreakWhiteSpace (\x{A0}) to ASCII Space - // Tab to ASCII Space - // 統整連續空格為一個 ASCII 空格 - strRAW.regReplace(pattern: #"( +| +| +|\t+)+"#, replaceWith: " ") - strRAW.regReplace(pattern: #"(^ | $)"#, replaceWith: "") // 去除行尾行首空格 - strRAW.regReplace(pattern: #"(\f+|\r+|\n+)+"#, replaceWith: "\n") // CR & Form Feed to LF, 且去除重複行 - strRAW.regReplace(pattern: #"^(#.*|.*#WIN32.*)$"#, replaceWith: "") // 以#開頭的行都淨空+去掉所有 WIN32 特有的行 - // 正式整理格式,現在就開始去重複: - let arrData = Array( - NSOrderedSet(array: strRAW.components(separatedBy: "\n")).array as! [String]) - var varLineData = "" - for lineData in arrData { - // 簡體中文的話,提取 1,2,4;繁體中文的話,提取 1,3,4。 - let varLineDataPre = lineData.components(separatedBy: " ").prefix(isCHS ? 2 : 1) - .joined( - separator: "\t") - let varLineDataPost = lineData.components(separatedBy: " ").suffix(isCHS ? 1 : 2) - .joined( - separator: "\t") - varLineData = varLineDataPre + "\t" + varLineDataPost - let arrLineData = varLineData.components(separatedBy: " ") - var varLineDataProcessed = "" - var count = 0 - for currentCell in arrLineData { - count += 1 - if count < 3 { - varLineDataProcessed += currentCell + "\t" - } else if count < arrLineData.count { - varLineDataProcessed += currentCell + "-" - } else { - varLineDataProcessed += currentCell - } - } - // 然後直接乾脆就轉成 Entry 吧。 - let arrCells: [String] = varLineDataProcessed.components(separatedBy: "\t") - count = 0 // 不需要再定義,因為之前已經有定義過了。 - var phone = "" - var phrase = "" - var occurrence = 0 - for cell in arrCells { - count += 1 - switch count { - case 1: phrase = cell - case 3: phone = cell - case 2: occurrence = Int(cell) ?? 0 - default: break - } - } - if phrase != "" { // 廢掉空數據;之後無須再這樣處理。 - arrEntryRAW += [ - Entry( - valPhone: phone, valPhrase: phrase, valWeight: 0.0, - valCount: occurrence - ) - ] - } - } - NSLog(" - \(i18n): 成功生成單字語料辭典(權重待計算)。") - return arrEntryRAW + var arrEntryRAW: [Entry] = [] + var strRAW = "" + let i18n: String = isCHS ? "簡體中文" : "繁體中文" + // 讀取內容 + do { + strRAW += try String(contentsOfFile: urlKanjiCore, encoding: .utf8) + } catch { + NSLog(" - Exception happened when reading raw core kanji data.") + return [] + } + // 預處理格式 + strRAW = strRAW.replacingOccurrences(of: " #MACOS", with: "") // 去掉 macOS 標記 + // CJKWhiteSpace (\x{3000}) to ASCII Space + // NonBreakWhiteSpace (\x{A0}) to ASCII Space + // Tab to ASCII Space + // 統整連續空格為一個 ASCII 空格 + strRAW.regReplace(pattern: #"( +| +| +|\t+)+"#, replaceWith: " ") + strRAW.regReplace(pattern: #"(^ | $)"#, replaceWith: "") // 去除行尾行首空格 + strRAW.regReplace(pattern: #"(\f+|\r+|\n+)+"#, replaceWith: "\n") // CR & Form Feed to LF, 且去除重複行 + strRAW.regReplace(pattern: #"^(#.*|.*#WIN32.*)$"#, replaceWith: "") // 以#開頭的行都淨空+去掉所有 WIN32 特有的行 + // 正式整理格式,現在就開始去重複: + let arrData = Array( + NSOrderedSet(array: strRAW.components(separatedBy: "\n")).array as! [String]) + var varLineData = "" + for lineData in arrData { + // 簡體中文的話,提取 1,2,4;繁體中文的話,提取 1,3,4。 + let varLineDataPre = lineData.components(separatedBy: " ").prefix(isCHS ? 2 : 1) + .joined( + separator: "\t") + let varLineDataPost = lineData.components(separatedBy: " ").suffix(isCHS ? 1 : 2) + .joined( + separator: "\t") + varLineData = varLineDataPre + "\t" + varLineDataPost + let arrLineData = varLineData.components(separatedBy: " ") + var varLineDataProcessed = "" + var count = 0 + for currentCell in arrLineData { + count += 1 + if count < 3 { + varLineDataProcessed += currentCell + "\t" + } else if count < arrLineData.count { + varLineDataProcessed += currentCell + "-" + } else { + varLineDataProcessed += currentCell + } + } + // 然後直接乾脆就轉成 Entry 吧。 + let arrCells: [String] = varLineDataProcessed.components(separatedBy: "\t") + count = 0 // 不需要再定義,因為之前已經有定義過了。 + var phone = "" + var phrase = "" + var occurrence = 0 + for cell in arrCells { + count += 1 + switch count { + case 1: phrase = cell + case 3: phone = cell + case 2: occurrence = Int(cell) ?? 0 + default: break + } + } + if phrase != "" { // 廢掉空數據;之後無須再這樣處理。 + arrEntryRAW += [ + Entry( + valPhone: phone, valPhrase: phrase, valWeight: 0.0, + valCount: occurrence + ) + ] + } + } + NSLog(" - \(i18n): 成功生成單字語料辭典(權重待計算)。") + return arrEntryRAW } // MARK: - 載入非漢字檔案且輸出陣列 func rawDictForNonKanjis(isCHS: Bool) -> [Entry] { - var arrEntryRAW: [Entry] = [] - var strRAW = "" - let i18n: String = isCHS ? "簡體中文" : "繁體中文" - // 讀取內容 - do { - strRAW += try String(contentsOfFile: urlMiscBPMF, encoding: .utf8) - strRAW += "\n" - strRAW += try String(contentsOfFile: urlMiscNonKanji, encoding: .utf8) - } catch { - NSLog(" - Exception happened when reading raw core kanji data.") - return [] - } - // 預處理格式 - strRAW = strRAW.replacingOccurrences(of: " #MACOS", with: "") // 去掉 macOS 標記 - // CJKWhiteSpace (\x{3000}) to ASCII Space - // NonBreakWhiteSpace (\x{A0}) to ASCII Space - // Tab to ASCII Space - // 統整連續空格為一個 ASCII 空格 - strRAW.regReplace(pattern: #"( +| +| +|\t+)+"#, replaceWith: " ") - strRAW.regReplace(pattern: #"(^ | $)"#, replaceWith: "") // 去除行尾行首空格 - strRAW.regReplace(pattern: #"(\f+|\r+|\n+)+"#, replaceWith: "\n") // CR & Form Feed to LF, 且去除重複行 - strRAW.regReplace(pattern: #"^(#.*|.*#WIN32.*)$"#, replaceWith: "") // 以#開頭的行都淨空+去掉所有 WIN32 特有的行 - // 正式整理格式,現在就開始去重複: - let arrData = Array( - NSOrderedSet(array: strRAW.components(separatedBy: "\n")).array as! [String]) - var varLineData = "" - for lineData in arrData { - varLineData = lineData - // 先完成某兩步需要分行處理才能完成的格式整理。 - varLineData = varLineData.components(separatedBy: " ").prefix(3).joined( - separator: "\t") // 提取前三欄的內容。 - let arrLineData = varLineData.components(separatedBy: " ") - var varLineDataProcessed = "" - var count = 0 - for currentCell in arrLineData { - count += 1 - if count < 3 { - varLineDataProcessed += currentCell + "\t" - } else if count < arrLineData.count { - varLineDataProcessed += currentCell + "-" - } else { - varLineDataProcessed += currentCell - } - } - // 然後直接乾脆就轉成 Entry 吧。 - let arrCells: [String] = varLineDataProcessed.components(separatedBy: "\t") - count = 0 // 不需要再定義,因為之前已經有定義過了。 - var phone = "" - var phrase = "" - var occurrence = 0 - for cell in arrCells { - count += 1 - switch count { - case 1: phrase = cell - case 3: phone = cell - case 2: occurrence = Int(cell) ?? 0 - default: break - } - } - if phrase != "" { // 廢掉空數據;之後無須再這樣處理。 - arrEntryRAW += [ - Entry( - valPhone: phone, valPhrase: phrase, valWeight: 0.0, - valCount: occurrence - ) - ] - } - } - NSLog(" - \(i18n): 成功生成非漢字語料辭典(權重待計算)。") - return arrEntryRAW + var arrEntryRAW: [Entry] = [] + var strRAW = "" + let i18n: String = isCHS ? "簡體中文" : "繁體中文" + // 讀取內容 + do { + strRAW += try String(contentsOfFile: urlMiscBPMF, encoding: .utf8) + strRAW += "\n" + strRAW += try String(contentsOfFile: urlMiscNonKanji, encoding: .utf8) + } catch { + NSLog(" - Exception happened when reading raw core kanji data.") + return [] + } + // 預處理格式 + strRAW = strRAW.replacingOccurrences(of: " #MACOS", with: "") // 去掉 macOS 標記 + // CJKWhiteSpace (\x{3000}) to ASCII Space + // NonBreakWhiteSpace (\x{A0}) to ASCII Space + // Tab to ASCII Space + // 統整連續空格為一個 ASCII 空格 + strRAW.regReplace(pattern: #"( +| +| +|\t+)+"#, replaceWith: " ") + strRAW.regReplace(pattern: #"(^ | $)"#, replaceWith: "") // 去除行尾行首空格 + strRAW.regReplace(pattern: #"(\f+|\r+|\n+)+"#, replaceWith: "\n") // CR & Form Feed to LF, 且去除重複行 + strRAW.regReplace(pattern: #"^(#.*|.*#WIN32.*)$"#, replaceWith: "") // 以#開頭的行都淨空+去掉所有 WIN32 特有的行 + // 正式整理格式,現在就開始去重複: + let arrData = Array( + NSOrderedSet(array: strRAW.components(separatedBy: "\n")).array as! [String]) + var varLineData = "" + for lineData in arrData { + varLineData = lineData + // 先完成某兩步需要分行處理才能完成的格式整理。 + varLineData = varLineData.components(separatedBy: " ").prefix(3).joined( + separator: "\t") // 提取前三欄的內容。 + let arrLineData = varLineData.components(separatedBy: " ") + var varLineDataProcessed = "" + var count = 0 + for currentCell in arrLineData { + count += 1 + if count < 3 { + varLineDataProcessed += currentCell + "\t" + } else if count < arrLineData.count { + varLineDataProcessed += currentCell + "-" + } else { + varLineDataProcessed += currentCell + } + } + // 然後直接乾脆就轉成 Entry 吧。 + let arrCells: [String] = varLineDataProcessed.components(separatedBy: "\t") + count = 0 // 不需要再定義,因為之前已經有定義過了。 + var phone = "" + var phrase = "" + var occurrence = 0 + for cell in arrCells { + count += 1 + switch count { + case 1: phrase = cell + case 3: phone = cell + case 2: occurrence = Int(cell) ?? 0 + default: break + } + } + if phrase != "" { // 廢掉空數據;之後無須再這樣處理。 + arrEntryRAW += [ + Entry( + valPhone: phone, valPhrase: phrase, valWeight: 0.0, + valCount: occurrence + ) + ] + } + } + NSLog(" - \(i18n): 成功生成非漢字語料辭典(權重待計算)。") + return arrEntryRAW } func weightAndSort(_ arrStructUncalculated: [Entry], isCHS: Bool) -> [Entry] { - let i18n: String = isCHS ? "簡體中文" : "繁體中文" - var arrStructCalculated: [Entry] = [] - let fscale: Float = 2.7 - var norm: Float = 0.0 - for entry in arrStructUncalculated { - if entry.valCount >= 0 { - norm += fscale ** (Float(entry.valPhrase.count) / 3.0 - 1.0) - * Float(entry.valCount) - } - } - // norm 計算完畢,開始將 norm 作為新的固定常數來為每個詞條記錄計算權重。 - // 將新酷音的詞語出現次數數據轉換成小麥引擎可讀的數據形式。 - // 對出現次數小於 1 的詞條,將 0 當成 0.5 來處理、以防止除零。 - for entry in arrStructUncalculated { - var weight: Float = 0 - switch entry.valCount { - case -2: // 拗音假名 - weight = -13 - case -1: // 單個假名 - weight = -13 - case 0: // 墊底低頻漢字與詞語 - weight = log10( - fscale ** (Float(entry.valPhrase.count) / 3.0 - 1.0) * 0.5 / norm) - default: - weight = log10( - fscale ** (Float(entry.valPhrase.count) / 3.0 - 1.0) - * Float(entry.valCount) / norm) // Credit: MJHsieh. - } - let weightRounded: Float = weight.rounded(toPlaces: 3) // 為了節省生成的檔案體積,僅保留小數點後三位。 - arrStructCalculated += [ - Entry( - valPhone: entry.valPhone, valPhrase: entry.valPhrase, valWeight: weightRounded, - valCount: entry.valCount - ) - ] - } - NSLog(" - \(i18n): 成功計算權重。") - // ========================================== - // 接下來是排序,先按照注音遞減排序一遍、再按照權重遞減排序一遍。 - let arrStructSorted: [Entry] = arrStructCalculated.sorted(by: { lhs, rhs -> Bool in - (lhs.valPhone, rhs.valCount) < (rhs.valPhone, lhs.valCount) - }) - NSLog(" - \(i18n): 排序整理完畢,準備編譯要寫入的檔案內容。") - return arrStructSorted + let i18n: String = isCHS ? "簡體中文" : "繁體中文" + var arrStructCalculated: [Entry] = [] + let fscale: Float = 2.7 + var norm: Float = 0.0 + for entry in arrStructUncalculated { + if entry.valCount >= 0 { + norm += fscale ** (Float(entry.valPhrase.count) / 3.0 - 1.0) + * Float(entry.valCount) + } + } + // norm 計算完畢,開始將 norm 作為新的固定常數來為每個詞條記錄計算權重。 + // 將新酷音的詞語出現次數數據轉換成小麥引擎可讀的數據形式。 + // 對出現次數小於 1 的詞條,將 0 當成 0.5 來處理、以防止除零。 + for entry in arrStructUncalculated { + var weight: Float = 0 + switch entry.valCount { + case -2: // 拗音假名 + weight = -13 + case -1: // 單個假名 + weight = -13 + case 0: // 墊底低頻漢字與詞語 + weight = log10( + fscale ** (Float(entry.valPhrase.count) / 3.0 - 1.0) * 0.5 / norm) + default: + weight = log10( + fscale ** (Float(entry.valPhrase.count) / 3.0 - 1.0) + * Float(entry.valCount) / norm) // Credit: MJHsieh. + } + let weightRounded: Float = weight.rounded(toPlaces: 3) // 為了節省生成的檔案體積,僅保留小數點後三位。 + arrStructCalculated += [ + Entry( + valPhone: entry.valPhone, valPhrase: entry.valPhrase, valWeight: weightRounded, + valCount: entry.valCount + ) + ] + } + NSLog(" - \(i18n): 成功計算權重。") + // ========================================== + // 接下來是排序,先按照注音遞減排序一遍、再按照權重遞減排序一遍。 + let arrStructSorted: [Entry] = arrStructCalculated.sorted(by: { lhs, rhs -> Bool in + (lhs.valPhone, rhs.valCount) < (rhs.valPhone, lhs.valCount) + }) + NSLog(" - \(i18n): 排序整理完畢,準備編譯要寫入的檔案內容。") + return arrStructSorted } func fileOutput(isCHS: Bool) { - let i18n: String = isCHS ? "簡體中文" : "繁體中文" - let pathOutput = urlCurrentFolder.appendingPathComponent( - isCHS ? urlOutputCHS : urlOutputCHT) - var strPrintLine = "" - // 讀取標點內容 - do { - strPrintLine += try String(contentsOfFile: urlPunctuation, encoding: .utf8) - } catch { - NSLog(" - \(i18n): Exception happened when reading raw punctuation data.") - } - NSLog(" - \(i18n): 成功插入標點符號與西文字母數據。") - // 統合辭典內容 - var arrStructUnified: [Entry] = [] - arrStructUnified += rawDictForKanjis(isCHS: isCHS) - arrStructUnified += rawDictForNonKanjis(isCHS: isCHS) - arrStructUnified += rawDictForPhrases(isCHS: isCHS) - // 計算權重且排序 - arrStructUnified = weightAndSort(arrStructUnified, isCHS: isCHS) + let i18n: String = isCHS ? "簡體中文" : "繁體中文" + let pathOutput = urlCurrentFolder.appendingPathComponent( + isCHS ? urlOutputCHS : urlOutputCHT) + var strPrintLine = "" + // 讀取標點內容 + do { + strPrintLine += try String(contentsOfFile: urlPunctuation, encoding: .utf8) + } catch { + NSLog(" - \(i18n): Exception happened when reading raw punctuation data.") + } + NSLog(" - \(i18n): 成功插入標點符號與西文字母數據。") + // 統合辭典內容 + var arrStructUnified: [Entry] = [] + arrStructUnified += rawDictForKanjis(isCHS: isCHS) + arrStructUnified += rawDictForNonKanjis(isCHS: isCHS) + arrStructUnified += rawDictForPhrases(isCHS: isCHS) + // 計算權重且排序 + arrStructUnified = weightAndSort(arrStructUnified, isCHS: isCHS) - for entry in arrStructUnified { - strPrintLine += - entry.valPhone + " " + entry.valPhrase + " " + String(entry.valWeight) - + "\n" - } - NSLog(" - \(i18n): 要寫入檔案的內容編譯完畢。") - do { - try strPrintLine.write(to: pathOutput, atomically: false, encoding: .utf8) - } catch { - NSLog(" - \(i18n): Error on writing strings to file: \(error)") - } - NSLog(" - \(i18n): 寫入完成。") + for entry in arrStructUnified { + strPrintLine += + entry.valPhone + " " + entry.valPhrase + " " + String(entry.valWeight) + + "\n" + } + NSLog(" - \(i18n): 要寫入檔案的內容編譯完畢。") + do { + try strPrintLine.write(to: pathOutput, atomically: false, encoding: .utf8) + } catch { + NSLog(" - \(i18n): Error on writing strings to file: \(error)") + } + NSLog(" - \(i18n): 寫入完成。") } // MARK: - 主执行绪 func main() { - NSLog("// 準備編譯繁體中文核心語料檔案。") - fileOutput(isCHS: false) - NSLog("// 準備編譯簡體中文核心語料檔案。") - fileOutput(isCHS: true) + NSLog("// 準備編譯繁體中文核心語料檔案。") + fileOutput(isCHS: false) + NSLog("// 準備編譯簡體中文核心語料檔案。") + fileOutput(isCHS: true) } main() diff --git a/Installer/AppDelegate.swift b/Installer/AppDelegate.swift index fcfeaef7..b4f084d1 100644 --- a/Installer/AppDelegate.swift +++ b/Installer/AppDelegate.swift @@ -31,11 +31,11 @@ private let kTargetType = "app" private let kTargetBundle = "vChewing.app" private let urlDestinationPartial = FileManager.default.urls( - for: .inputMethodsDirectory, in: .userDomainMask + for: .inputMethodsDirectory, in: .userDomainMask )[0] private let urlTargetPartial = urlDestinationPartial.appendingPathComponent(kTargetBundle) private let urlTargetFullBinPartial = urlTargetPartial.appendingPathComponent("Contents/MacOS/") - .appendingPathComponent(kTargetBin) + .appendingPathComponent(kTargetBin) private let kDestinationPartial = urlDestinationPartial.path private let kTargetPartialPath = urlTargetPartial.path @@ -47,315 +47,315 @@ private let kTranslocationRemovalDeadline: TimeInterval = 60.0 @NSApplicationMain @objc(AppDelegate) class AppDelegate: NSWindowController, NSApplicationDelegate { - @IBOutlet private var installButton: NSButton! - @IBOutlet private var cancelButton: NSButton! - @IBOutlet private var progressSheet: NSWindow! - @IBOutlet private var progressIndicator: NSProgressIndicator! - @IBOutlet private var appVersionLabel: NSTextField! - @IBOutlet private var appCopyrightLabel: NSTextField! - @IBOutlet private var appEULAContent: NSTextView! + @IBOutlet private var installButton: NSButton! + @IBOutlet private var cancelButton: NSButton! + @IBOutlet private var progressSheet: NSWindow! + @IBOutlet private var progressIndicator: NSProgressIndicator! + @IBOutlet private var appVersionLabel: NSTextField! + @IBOutlet private var appCopyrightLabel: NSTextField! + @IBOutlet private var appEULAContent: NSTextView! - private var archiveUtil: ArchiveUtil? - private var installingVersion = "" - private var upgrading = false - private var translocationRemovalStartTime: Date? - private var currentVersionNumber: Int = 0 + private var archiveUtil: ArchiveUtil? + private var installingVersion = "" + private var upgrading = false + private var translocationRemovalStartTime: Date? + private var currentVersionNumber: Int = 0 - func runAlertPanel(title: String, message: String, buttonTitle: String) { - let alert = NSAlert() - alert.alertStyle = .informational - alert.messageText = title - alert.informativeText = message - alert.addButton(withTitle: buttonTitle) - alert.runModal() - } + func runAlertPanel(title: String, message: String, buttonTitle: String) { + let alert = NSAlert() + alert.alertStyle = .informational + alert.messageText = title + alert.informativeText = message + alert.addButton(withTitle: buttonTitle) + alert.runModal() + } - func applicationDidFinishLaunching(_: Notification) { - guard - let installingVersion = Bundle.main.infoDictionary?[kCFBundleVersionKey as String] - as? String, - let versionString = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String - else { - return - } - self.installingVersion = installingVersion - archiveUtil = ArchiveUtil(appName: kTargetBin, targetAppBundleName: kTargetBundle) - _ = archiveUtil?.validateIfNotarizedArchiveExists() + func applicationDidFinishLaunching(_: Notification) { + guard + let installingVersion = Bundle.main.infoDictionary?[kCFBundleVersionKey as String] + as? String, + let versionString = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String + else { + return + } + self.installingVersion = installingVersion + archiveUtil = ArchiveUtil(appName: kTargetBin, targetAppBundleName: kTargetBundle) + _ = archiveUtil?.validateIfNotarizedArchiveExists() - cancelButton.nextKeyView = installButton - installButton.nextKeyView = cancelButton - if let cell = installButton.cell as? NSButtonCell { - window?.defaultButtonCell = cell - } + cancelButton.nextKeyView = installButton + installButton.nextKeyView = cancelButton + if let cell = installButton.cell as? NSButtonCell { + window?.defaultButtonCell = cell + } - if let copyrightLabel = Bundle.main.localizedInfoDictionary?["NSHumanReadableCopyright"] - as? String - { - appCopyrightLabel.stringValue = copyrightLabel - } - if let eulaContent = Bundle.main.localizedInfoDictionary?["CFEULAContent"] as? String { - appEULAContent.string = eulaContent - } - appVersionLabel.stringValue = String( - format: "%@ Build %@", versionString, installingVersion - ) + if let copyrightLabel = Bundle.main.localizedInfoDictionary?["NSHumanReadableCopyright"] + as? String + { + appCopyrightLabel.stringValue = copyrightLabel + } + if let eulaContent = Bundle.main.localizedInfoDictionary?["CFEULAContent"] as? String { + appEULAContent.string = eulaContent + } + appVersionLabel.stringValue = String( + format: "%@ Build %@", versionString, installingVersion + ) - window?.title = String( - format: NSLocalizedString("%@ (for version %@, r%@)", comment: ""), window?.title ?? "", - versionString, installingVersion - ) - window?.standardWindowButton(.closeButton)?.isHidden = true - window?.standardWindowButton(.miniaturizeButton)?.isHidden = true - window?.standardWindowButton(.zoomButton)?.isHidden = true + window?.title = String( + format: NSLocalizedString("%@ (for version %@, r%@)", comment: ""), window?.title ?? "", + versionString, installingVersion + ) + window?.standardWindowButton(.closeButton)?.isHidden = true + window?.standardWindowButton(.miniaturizeButton)?.isHidden = true + window?.standardWindowButton(.zoomButton)?.isHidden = true - if FileManager.default.fileExists( - atPath: (kTargetPartialPath as NSString).expandingTildeInPath) - { - let currentBundle = Bundle(path: (kTargetPartialPath as NSString).expandingTildeInPath) - let shortVersion = - currentBundle?.infoDictionary?["CFBundleShortVersionString"] as? String - let currentVersion = - currentBundle?.infoDictionary?[kCFBundleVersionKey as String] as? String - currentVersionNumber = (currentVersion as NSString?)?.integerValue ?? 0 - if shortVersion != nil, let currentVersion = currentVersion, - currentVersion.compare(installingVersion, options: .numeric) == .orderedAscending - { - upgrading = true - } - } + if FileManager.default.fileExists( + atPath: (kTargetPartialPath as NSString).expandingTildeInPath) + { + let currentBundle = Bundle(path: (kTargetPartialPath as NSString).expandingTildeInPath) + let shortVersion = + currentBundle?.infoDictionary?["CFBundleShortVersionString"] as? String + let currentVersion = + currentBundle?.infoDictionary?[kCFBundleVersionKey as String] as? String + currentVersionNumber = (currentVersion as NSString?)?.integerValue ?? 0 + if shortVersion != nil, let currentVersion = currentVersion, + currentVersion.compare(installingVersion, options: .numeric) == .orderedAscending + { + upgrading = true + } + } - if upgrading { - installButton.title = NSLocalizedString("Upgrade", comment: "") - } + if upgrading { + installButton.title = NSLocalizedString("Upgrade", comment: "") + } - window?.center() - window?.orderFront(self) - NSApp.activate(ignoringOtherApps: true) - } + window?.center() + window?.orderFront(self) + NSApp.activate(ignoringOtherApps: true) + } - @IBAction func agreeAndInstallAction(_: AnyObject) { - cancelButton.isEnabled = false - installButton.isEnabled = false - removeThenInstallInputMethod() - } + @IBAction func agreeAndInstallAction(_: AnyObject) { + cancelButton.isEnabled = false + installButton.isEnabled = false + removeThenInstallInputMethod() + } - @objc func timerTick(_ timer: Timer) { - let elapsed = Date().timeIntervalSince(translocationRemovalStartTime ?? Date()) - if elapsed >= kTranslocationRemovalDeadline { - timer.invalidate() - window?.endSheet(progressSheet, returnCode: .cancel) - } else if appBundleChronoshiftedToARandomizedPath(kTargetPartialPath) == false { - progressIndicator.doubleValue = 1.0 - timer.invalidate() - window?.endSheet(progressSheet, returnCode: .continue) - } - } + @objc func timerTick(_ timer: Timer) { + let elapsed = Date().timeIntervalSince(translocationRemovalStartTime ?? Date()) + if elapsed >= kTranslocationRemovalDeadline { + timer.invalidate() + window?.endSheet(progressSheet, returnCode: .cancel) + } else if appBundleChronoshiftedToARandomizedPath(kTargetPartialPath) == false { + progressIndicator.doubleValue = 1.0 + timer.invalidate() + window?.endSheet(progressSheet, returnCode: .continue) + } + } - func removeThenInstallInputMethod() { - if FileManager.default.fileExists( - atPath: (kTargetPartialPath as NSString).expandingTildeInPath) - == false - { - installInputMethod( - previousExists: false, previousVersionNotFullyDeactivatedWarning: false - ) - return - } + func removeThenInstallInputMethod() { + if FileManager.default.fileExists( + atPath: (kTargetPartialPath as NSString).expandingTildeInPath) + == false + { + installInputMethod( + previousExists: false, previousVersionNotFullyDeactivatedWarning: false + ) + return + } - let shouldWaitForTranslocationRemoval = - appBundleChronoshiftedToARandomizedPath(kTargetPartialPath) - && (window?.responds(to: #selector(NSWindow.beginSheet(_:completionHandler:))) ?? false) + let shouldWaitForTranslocationRemoval = + appBundleChronoshiftedToARandomizedPath(kTargetPartialPath) + && (window?.responds(to: #selector(NSWindow.beginSheet(_:completionHandler:))) ?? false) - // 將既存輸入法扔到垃圾桶內 - do { - let sourceDir = (kDestinationPartial as NSString).expandingTildeInPath - let fileManager = FileManager.default - let fileURLString = String(format: "%@/%@", sourceDir, kTargetBundle) - let fileURL = URL(fileURLWithPath: fileURLString) + // 將既存輸入法扔到垃圾桶內 + do { + let sourceDir = (kDestinationPartial as NSString).expandingTildeInPath + let fileManager = FileManager.default + let fileURLString = String(format: "%@/%@", sourceDir, kTargetBundle) + let fileURL = URL(fileURLWithPath: fileURLString) - // 檢查檔案是否存在 - if fileManager.fileExists(atPath: fileURLString) { - // 塞入垃圾桶 - try fileManager.trashItem(at: fileURL, resultingItemURL: nil) - } else { - NSLog("File does not exist") - } + // 檢查檔案是否存在 + if fileManager.fileExists(atPath: fileURLString) { + // 塞入垃圾桶 + try fileManager.trashItem(at: fileURL, resultingItemURL: nil) + } else { + NSLog("File does not exist") + } - } catch let error as NSError { - NSLog("An error took place: \(error)") - } + } catch let error as NSError { + NSLog("An error took place: \(error)") + } - let killTask = Process() - killTask.launchPath = "/usr/bin/killall" - killTask.arguments = ["-9", kTargetBin] - killTask.launch() - killTask.waitUntilExit() + let killTask = Process() + killTask.launchPath = "/usr/bin/killall" + killTask.arguments = ["-9", kTargetBin] + killTask.launch() + killTask.waitUntilExit() - if shouldWaitForTranslocationRemoval { - progressIndicator.startAnimation(self) - window?.beginSheet(progressSheet) { returnCode in - DispatchQueue.main.async { - if returnCode == .continue { - self.installInputMethod( - previousExists: true, - previousVersionNotFullyDeactivatedWarning: false - ) - } else { - self.installInputMethod( - previousExists: true, - previousVersionNotFullyDeactivatedWarning: true - ) - } - } - } + if shouldWaitForTranslocationRemoval { + progressIndicator.startAnimation(self) + window?.beginSheet(progressSheet) { returnCode in + DispatchQueue.main.async { + if returnCode == .continue { + self.installInputMethod( + previousExists: true, + previousVersionNotFullyDeactivatedWarning: false + ) + } else { + self.installInputMethod( + previousExists: true, + previousVersionNotFullyDeactivatedWarning: true + ) + } + } + } - translocationRemovalStartTime = Date() - Timer.scheduledTimer( - timeInterval: kTranslocationRemovalTickInterval, target: self, - selector: #selector(timerTick(_:)), userInfo: nil, repeats: true - ) - } else { - installInputMethod( - previousExists: false, previousVersionNotFullyDeactivatedWarning: false - ) - } - } + translocationRemovalStartTime = Date() + Timer.scheduledTimer( + timeInterval: kTranslocationRemovalTickInterval, target: self, + selector: #selector(timerTick(_:)), userInfo: nil, repeats: true + ) + } else { + installInputMethod( + previousExists: false, previousVersionNotFullyDeactivatedWarning: false + ) + } + } - func installInputMethod( - previousExists _: Bool, previousVersionNotFullyDeactivatedWarning warning: Bool - ) { - guard - let targetBundle = archiveUtil?.unzipNotarizedArchive() - ?? Bundle.main.path(forResource: kTargetBin, ofType: kTargetType) - else { - return - } - let cpTask = Process() - cpTask.launchPath = "/bin/cp" - cpTask.arguments = [ - "-R", targetBundle, (kDestinationPartial as NSString).expandingTildeInPath, - ] - cpTask.launch() - cpTask.waitUntilExit() + func installInputMethod( + previousExists _: Bool, previousVersionNotFullyDeactivatedWarning warning: Bool + ) { + guard + let targetBundle = archiveUtil?.unzipNotarizedArchive() + ?? Bundle.main.path(forResource: kTargetBin, ofType: kTargetType) + else { + return + } + let cpTask = Process() + cpTask.launchPath = "/bin/cp" + cpTask.arguments = [ + "-R", targetBundle, (kDestinationPartial as NSString).expandingTildeInPath, + ] + cpTask.launch() + cpTask.waitUntilExit() - if cpTask.terminationStatus != 0 { - runAlertPanel( - title: NSLocalizedString("Install Failed", comment: ""), - message: NSLocalizedString("Cannot copy the file to the destination.", comment: ""), - buttonTitle: NSLocalizedString("Cancel", comment: "") - ) - endAppWithDelay() - } + if cpTask.terminationStatus != 0 { + runAlertPanel( + title: NSLocalizedString("Install Failed", comment: ""), + message: NSLocalizedString("Cannot copy the file to the destination.", comment: ""), + buttonTitle: NSLocalizedString("Cancel", comment: "") + ) + endAppWithDelay() + } - guard let imeBundle = Bundle(path: (kTargetPartialPath as NSString).expandingTildeInPath), - let imeIdentifier = imeBundle.bundleIdentifier - else { - endAppWithDelay() - return - } + guard let imeBundle = Bundle(path: (kTargetPartialPath as NSString).expandingTildeInPath), + let imeIdentifier = imeBundle.bundleIdentifier + else { + endAppWithDelay() + return + } - let imeBundleURL = imeBundle.bundleURL - var inputSource = InputSourceHelper.inputSource(for: imeIdentifier) + let imeBundleURL = imeBundle.bundleURL + var inputSource = InputSourceHelper.inputSource(for: imeIdentifier) - if inputSource == nil { - NSLog("Registering input source \(imeIdentifier) at \(imeBundleURL.absoluteString).") - let status = InputSourceHelper.registerTnputSource(at: imeBundleURL) - if !status { - let message = String( - format: NSLocalizedString( - "Cannot find input source %@ after registration.", comment: "" - ), - imeIdentifier - ) - runAlertPanel( - title: NSLocalizedString("Fatal Error", comment: ""), message: message, - buttonTitle: NSLocalizedString("Abort", comment: "") - ) - endAppWithDelay() - return - } + if inputSource == nil { + NSLog("Registering input source \(imeIdentifier) at \(imeBundleURL.absoluteString).") + let status = InputSourceHelper.registerTnputSource(at: imeBundleURL) + if !status { + let message = String( + format: NSLocalizedString( + "Cannot find input source %@ after registration.", comment: "" + ), + imeIdentifier + ) + runAlertPanel( + title: NSLocalizedString("Fatal Error", comment: ""), message: message, + buttonTitle: NSLocalizedString("Abort", comment: "") + ) + endAppWithDelay() + return + } - inputSource = InputSourceHelper.inputSource(for: imeIdentifier) - if inputSource == nil { - let message = String( - format: NSLocalizedString( - "Cannot find input source %@ after registration.", comment: "" - ), - imeIdentifier - ) - runAlertPanel( - title: NSLocalizedString("Fatal Error", comment: ""), message: message, - buttonTitle: NSLocalizedString("Abort", comment: "") - ) - } - } + inputSource = InputSourceHelper.inputSource(for: imeIdentifier) + if inputSource == nil { + let message = String( + format: NSLocalizedString( + "Cannot find input source %@ after registration.", comment: "" + ), + imeIdentifier + ) + runAlertPanel( + title: NSLocalizedString("Fatal Error", comment: ""), message: message, + buttonTitle: NSLocalizedString("Abort", comment: "") + ) + } + } - var isMacOS12OrAbove = false - if #available(macOS 12.0, *) { - NSLog("macOS 12 or later detected.") - isMacOS12OrAbove = true - } else { - NSLog("Installer runs with the pre-macOS 12 flow.") - } + var isMacOS12OrAbove = false + if #available(macOS 12.0, *) { + NSLog("macOS 12 or later detected.") + isMacOS12OrAbove = true + } else { + NSLog("Installer runs with the pre-macOS 12 flow.") + } - // If the IME is not enabled, enable it. Also, unconditionally enable it on macOS 12.0+, - // as the kTISPropertyInputSourceIsEnabled can still be true even if the IME is *not* - // enabled in the user's current set of IMEs (which means the IME does not show up in - // the user's input menu). + // If the IME is not enabled, enable it. Also, unconditionally enable it on macOS 12.0+, + // as the kTISPropertyInputSourceIsEnabled can still be true even if the IME is *not* + // enabled in the user's current set of IMEs (which means the IME does not show up in + // the user's input menu). - var mainInputSourceEnabled = InputSourceHelper.inputSourceEnabled(for: inputSource!) - if !mainInputSourceEnabled || isMacOS12OrAbove { - mainInputSourceEnabled = InputSourceHelper.enable(inputSource: inputSource!) - if mainInputSourceEnabled { - NSLog("Input method enabled: \(imeIdentifier)") - } else { - NSLog("Failed to enable input method: \(imeIdentifier)") - } - } + var mainInputSourceEnabled = InputSourceHelper.inputSourceEnabled(for: inputSource!) + if !mainInputSourceEnabled || isMacOS12OrAbove { + mainInputSourceEnabled = InputSourceHelper.enable(inputSource: inputSource!) + if mainInputSourceEnabled { + NSLog("Input method enabled: \(imeIdentifier)") + } else { + NSLog("Failed to enable input method: \(imeIdentifier)") + } + } - // Alert Panel - let ntfPostInstall = NSAlert() - if warning { - ntfPostInstall.messageText = NSLocalizedString("Attention", comment: "") - ntfPostInstall.informativeText = NSLocalizedString( - "vChewing is upgraded, but please log out or reboot for the new version to be fully functional.", - comment: "" - ) - ntfPostInstall.addButton(withTitle: NSLocalizedString("OK", comment: "")) - } else { - if !mainInputSourceEnabled, !isMacOS12OrAbove { - ntfPostInstall.messageText = NSLocalizedString("Warning", comment: "") - ntfPostInstall.informativeText = NSLocalizedString( - "Input method may not be fully enabled. Please enable it through System Preferences > Keyboard > Input Sources.", - comment: "" - ) - ntfPostInstall.addButton(withTitle: NSLocalizedString("Continue", comment: "")) - } else { - ntfPostInstall.messageText = NSLocalizedString( - "Installation Successful", comment: "" - ) - ntfPostInstall.informativeText = NSLocalizedString( - "vChewing is ready to use.", comment: "" - ) - ntfPostInstall.addButton(withTitle: NSLocalizedString("OK", comment: "")) - } - } - ntfPostInstall.beginSheetModal(for: window!) { _ in - self.endAppWithDelay() - } - } + // Alert Panel + let ntfPostInstall = NSAlert() + if warning { + ntfPostInstall.messageText = NSLocalizedString("Attention", comment: "") + ntfPostInstall.informativeText = NSLocalizedString( + "vChewing is upgraded, but please log out or reboot for the new version to be fully functional.", + comment: "" + ) + ntfPostInstall.addButton(withTitle: NSLocalizedString("OK", comment: "")) + } else { + if !mainInputSourceEnabled, !isMacOS12OrAbove { + ntfPostInstall.messageText = NSLocalizedString("Warning", comment: "") + ntfPostInstall.informativeText = NSLocalizedString( + "Input method may not be fully enabled. Please enable it through System Preferences > Keyboard > Input Sources.", + comment: "" + ) + ntfPostInstall.addButton(withTitle: NSLocalizedString("Continue", comment: "")) + } else { + ntfPostInstall.messageText = NSLocalizedString( + "Installation Successful", comment: "" + ) + ntfPostInstall.informativeText = NSLocalizedString( + "vChewing is ready to use.", comment: "" + ) + ntfPostInstall.addButton(withTitle: NSLocalizedString("OK", comment: "")) + } + } + ntfPostInstall.beginSheetModal(for: window!) { _ in + self.endAppWithDelay() + } + } - func endAppWithDelay() { - DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) { - NSApp.terminate(self) - } - } + func endAppWithDelay() { + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) { + NSApp.terminate(self) + } + } - @IBAction func cancelAction(_: AnyObject) { - NSApp.terminate(self) - } + @IBAction func cancelAction(_: AnyObject) { + NSApp.terminate(self) + } - func windowWillClose(_: Notification) { - NSApp.terminate(self) - } + func windowWillClose(_: Notification) { + NSApp.terminate(self) + } } diff --git a/Installer/ArchiveUtil.swift b/Installer/ArchiveUtil.swift index e422428a..ccf02baf 100644 --- a/Installer/ArchiveUtil.swift +++ b/Installer/ArchiveUtil.swift @@ -27,109 +27,109 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import Cocoa struct ArchiveUtil { - var appName: String - var targetAppBundleName: String + var appName: String + var targetAppBundleName: String - init(appName: String, targetAppBundleName: String) { - self.appName = appName - self.targetAppBundleName = targetAppBundleName - } + init(appName: String, targetAppBundleName: String) { + self.appName = appName + self.targetAppBundleName = targetAppBundleName + } - // Returns YES if (1) a zip file under - // Resources/NotarizedArchives/$_appName-$bundleVersion.zip exists, and (2) if - // Resources/$_invalidAppBundleName does not exist. - func validateIfNotarizedArchiveExists() -> Bool { - guard let resourePath = Bundle.main.resourcePath, - let notarizedArchivesPath = notarizedArchivesPath, - let notarizedArchive = notarizedArchive, - let notarizedArchivesContent: [String] = try? FileManager.default.subpathsOfDirectory( - atPath: notarizedArchivesPath) - else { - return false - } + // Returns YES if (1) a zip file under + // Resources/NotarizedArchives/$_appName-$bundleVersion.zip exists, and (2) if + // Resources/$_invalidAppBundleName does not exist. + func validateIfNotarizedArchiveExists() -> Bool { + guard let resourePath = Bundle.main.resourcePath, + let notarizedArchivesPath = notarizedArchivesPath, + let notarizedArchive = notarizedArchive, + let notarizedArchivesContent: [String] = try? FileManager.default.subpathsOfDirectory( + atPath: notarizedArchivesPath) + else { + return false + } - let devModeAppBundlePath = (resourePath as NSString).appendingPathComponent(targetAppBundleName) - let count = notarizedArchivesContent.count - let notarizedArchiveExists = FileManager.default.fileExists(atPath: notarizedArchive) - let devModeAppBundleExists = FileManager.default.fileExists(atPath: devModeAppBundlePath) + let devModeAppBundlePath = (resourePath as NSString).appendingPathComponent(targetAppBundleName) + let count = notarizedArchivesContent.count + let notarizedArchiveExists = FileManager.default.fileExists(atPath: notarizedArchive) + let devModeAppBundleExists = FileManager.default.fileExists(atPath: devModeAppBundlePath) - if !notarizedArchivesContent.isEmpty { - // 這裡不用「count > 0」,因為該整數變數只要「!isEmpty」那就必定滿足這個條件。 - if count != 1 || !notarizedArchiveExists || devModeAppBundleExists { - let alert = NSAlert() - alert.alertStyle = .informational - alert.messageText = "Internal Error" - alert.informativeText = - "devMode installer, expected archive name: \(notarizedArchive), " - + "archive exists: \(notarizedArchiveExists), devMode app bundle exists: \(devModeAppBundleExists)" - alert.addButton(withTitle: "Terminate") - alert.runModal() - NSApp.terminate(nil) - } else { - return true - } - } + if !notarizedArchivesContent.isEmpty { + // 這裡不用「count > 0」,因為該整數變數只要「!isEmpty」那就必定滿足這個條件。 + if count != 1 || !notarizedArchiveExists || devModeAppBundleExists { + let alert = NSAlert() + alert.alertStyle = .informational + alert.messageText = "Internal Error" + alert.informativeText = + "devMode installer, expected archive name: \(notarizedArchive), " + + "archive exists: \(notarizedArchiveExists), devMode app bundle exists: \(devModeAppBundleExists)" + alert.addButton(withTitle: "Terminate") + alert.runModal() + NSApp.terminate(nil) + } else { + return true + } + } - if !devModeAppBundleExists { - let alert = NSAlert() - alert.alertStyle = .informational - alert.messageText = "Internal Error" - alert.informativeText = "Dev target bundle does not exist: \(devModeAppBundlePath)" - alert.addButton(withTitle: "Terminate") - alert.runModal() - NSApp.terminate(nil) - } + if !devModeAppBundleExists { + let alert = NSAlert() + alert.alertStyle = .informational + alert.messageText = "Internal Error" + alert.informativeText = "Dev target bundle does not exist: \(devModeAppBundlePath)" + alert.addButton(withTitle: "Terminate") + alert.runModal() + NSApp.terminate(nil) + } - return false - } + return false + } - func unzipNotarizedArchive() -> String? { - if !validateIfNotarizedArchiveExists() { - return nil - } - guard let notarizedArchive = notarizedArchive, - let resourcePath = Bundle.main.resourcePath - else { - return nil - } - let tempFilePath = (NSTemporaryDirectory() as NSString).appendingPathComponent( - UUID().uuidString) - let arguments: [String] = [notarizedArchive, "-d", tempFilePath] - let unzipTask = Process() - unzipTask.launchPath = "/usr/bin/unzip" - unzipTask.currentDirectoryPath = resourcePath - unzipTask.arguments = arguments - unzipTask.launch() - unzipTask.waitUntilExit() + func unzipNotarizedArchive() -> String? { + if !validateIfNotarizedArchiveExists() { + return nil + } + guard let notarizedArchive = notarizedArchive, + let resourcePath = Bundle.main.resourcePath + else { + return nil + } + let tempFilePath = (NSTemporaryDirectory() as NSString).appendingPathComponent( + UUID().uuidString) + let arguments: [String] = [notarizedArchive, "-d", tempFilePath] + let unzipTask = Process() + unzipTask.launchPath = "/usr/bin/unzip" + unzipTask.currentDirectoryPath = resourcePath + unzipTask.arguments = arguments + unzipTask.launch() + unzipTask.waitUntilExit() - assert(unzipTask.terminationStatus == 0, "Must successfully unzipped") - let result = (tempFilePath as NSString).appendingPathComponent(targetAppBundleName) - assert( - FileManager.default.fileExists(atPath: result), - "App bundle must be unzipped at \(result)." - ) - return result - } + assert(unzipTask.terminationStatus == 0, "Must successfully unzipped") + let result = (tempFilePath as NSString).appendingPathComponent(targetAppBundleName) + assert( + FileManager.default.fileExists(atPath: result), + "App bundle must be unzipped at \(result)." + ) + return result + } - private var notarizedArchivesPath: String? { - guard let resourePath = Bundle.main.resourcePath else { - return nil - } - let notarizedArchivesPath = (resourePath as NSString).appendingPathComponent( - "NotarizedArchives") - return notarizedArchivesPath - } + private var notarizedArchivesPath: String? { + guard let resourePath = Bundle.main.resourcePath else { + return nil + } + let notarizedArchivesPath = (resourePath as NSString).appendingPathComponent( + "NotarizedArchives") + return notarizedArchivesPath + } - private var notarizedArchive: String? { - guard let notarizedArchivesPath = notarizedArchivesPath, - let bundleVersion = Bundle.main.infoDictionary?[kCFBundleVersionKey as String] - as? String - else { - return nil - } - let notarizedArchiveBasename = "\(appName)-r\(bundleVersion).zip" - let notarizedArchive = (notarizedArchivesPath as NSString).appendingPathComponent( - notarizedArchiveBasename) - return notarizedArchive - } + private var notarizedArchive: String? { + guard let notarizedArchivesPath = notarizedArchivesPath, + let bundleVersion = Bundle.main.infoDictionary?[kCFBundleVersionKey as String] + as? String + else { + return nil + } + let notarizedArchiveBasename = "\(appName)-r\(bundleVersion).zip" + let notarizedArchive = (notarizedArchivesPath as NSString).appendingPathComponent( + notarizedArchiveBasename) + return notarizedArchive + } } diff --git a/Makefile b/Makefile index 92a7d187..5a9608c9 100644 --- a/Makefile +++ b/Makefile @@ -28,6 +28,7 @@ clang-format: clang-format-swift clang-format-cpp clang-format-swift: @git ls-files --exclude-standard | grep -E '\.swift$$' | xargs swift-format format --in-place --configuration ./.clang-format-swift.json --parallel + @git ls-files --exclude-standard | grep -E '\.swift$$' | xargs swift-format lint --configuration ./.clang-format-swift.json --parallel clang-format-cpp: @git ls-files --exclude-standard | grep -E '\.(cpp|hpp|c|cc|cxx|hxx|ixx|h|m|mm|hh)$$' | xargs clang-format -i diff --git a/Packages/SwiftyOpenCC/Package.swift b/Packages/SwiftyOpenCC/Package.swift index 470d63c1..d8c8cf5d 100644 --- a/Packages/SwiftyOpenCC/Package.swift +++ b/Packages/SwiftyOpenCC/Package.swift @@ -3,87 +3,83 @@ import PackageDescription let package = Package( - name: "SwiftyOpenCC", - products: [ - .library( - name: "OpenCC", - targets: ["OpenCC"] - ) - ], - targets: [ - .target( - name: "OpenCC", - dependencies: ["copencc"], - resources: [ - .copy("Dictionary") - ] - ), - .testTarget( - name: "OpenCCTests", - dependencies: ["OpenCC"], - resources: [ - .copy("benchmark"), - .copy("testcases"), - ] - ), - .target( - name: "copencc", - exclude: [ - "src/benchmark", - "src/tools", - "src/BinaryDictTest.cpp", - "src/Config.cpp", - "src/ConfigTest.cpp", - "src/ConversionChainTest.cpp", - "src/ConversionTest.cpp", - "src/DartsDictTest.cpp", - "src/DictGroupTest.cpp", - "src/MarisaDictTest.cpp", - "src/MaxMatchSegmentationTest.cpp", - "src/PhraseExtractTest.cpp", - "src/SerializedValuesTest.cpp", - "src/SimpleConverter.cpp", - "src/SimpleConverterTest.cpp", - "src/TextDictTest.cpp", - "src/UTF8StringSliceTest.cpp", - "src/UTF8UtilTest.cpp", - "deps/google-benchmark", - "deps/gtest-1.11.0", - "deps/pybind11-2.5.0", - "deps/rapidjson-1.1.0", - "deps/tclap-1.2.2", + name: "SwiftyOpenCC", + products: [ + .library( + name: "OpenCC", + targets: ["OpenCC"] + ) + ], + targets: [ + .target( + name: "OpenCC", + dependencies: ["copencc"], + resources: [ + .copy("Dictionary") + ] + ), + .testTarget( + name: "OpenCCTests", + dependencies: ["OpenCC"], + resources: [ + .copy("benchmark"), + .copy("testcases"), + ] + ), + .target( + name: "copencc", + exclude: [ + "src/benchmark", + "src/tools", + "src/BinaryDictTest.cpp", + "src/Config.cpp", + "src/ConfigTest.cpp", + "src/ConversionChainTest.cpp", + "src/ConversionTest.cpp", + "src/DartsDictTest.cpp", + "src/DictGroupTest.cpp", + "src/MarisaDictTest.cpp", + "src/MaxMatchSegmentationTest.cpp", + "src/PhraseExtractTest.cpp", + "src/SerializedValuesTest.cpp", + "src/SimpleConverter.cpp", + "src/SimpleConverterTest.cpp", + "src/TextDictTest.cpp", + "src/UTF8StringSliceTest.cpp", + "src/UTF8UtilTest.cpp", + "deps/google-benchmark", - "src/CmdLineOutput.hpp", - "src/Config.hpp", - "src/ConfigTestBase.hpp", - "src/DictGroupTestBase.hpp", - "src/SimpleConverter.hpp", - "src/TestUtils.hpp", - "src/TestUtilsUTF8.hpp", - "src/TextDictTestBase.hpp", - "src/py_opencc.cpp", + "src/CmdLineOutput.hpp", + "src/Config.hpp", + "src/ConfigTestBase.hpp", + "src/DictGroupTestBase.hpp", + "src/SimpleConverter.hpp", + "src/TestUtils.hpp", + "src/TestUtilsUTF8.hpp", + "src/TextDictTestBase.hpp", + "src/py_opencc.cpp", - // ??? - "src/README.md", - "src/CMakeLists.txt", - "deps/marisa-0.2.6/AUTHORS", - "deps/marisa-0.2.6/CMakeLists.txt", - "deps/marisa-0.2.6/COPYING.md", - "deps/marisa-0.2.6/README.md", - ], - sources: [ - "source.cpp", - "src", - "deps/marisa-0.2.6", - ], - cxxSettings: [ - .headerSearchPath("src"), - .headerSearchPath("deps/darts-clone"), - .headerSearchPath("deps/marisa-0.2.6/include"), - .headerSearchPath("deps/marisa-0.2.6/lib"), - .define("ENABLE_DARTS"), - ] - ), - ], - cxxLanguageStandard: .cxx14 + // ??? + "src/README.md", + "src/CMakeLists.txt", + "deps/marisa-0.2.6/AUTHORS", + "deps/marisa-0.2.6/CMakeLists.txt", + "deps/marisa-0.2.6/COPYING.md", + "deps/marisa-0.2.6/README.md", + ], + sources: [ + "source.cpp", + "src", + "deps/marisa-0.2.6", + ], + cxxSettings: [ + .headerSearchPath("src"), + .headerSearchPath("deps/darts-clone"), + .headerSearchPath("deps/marisa-0.2.6/include"), + .headerSearchPath("deps/marisa-0.2.6/lib"), + .define("ENABLE_DARTS"), + ] + ), + ], + cxxLanguageStandard: .cxx14 ) diff --git a/Packages/SwiftyOpenCC/Sources/OpenCC/ChineseConverter.swift b/Packages/SwiftyOpenCC/Sources/OpenCC/ChineseConverter.swift index 81678048..a35be81a 100644 --- a/Packages/SwiftyOpenCC/Sources/OpenCC/ChineseConverter.swift +++ b/Packages/SwiftyOpenCC/Sources/OpenCC/ChineseConverter.swift @@ -22,65 +22,65 @@ import copencc /// However, the string on which it is operating should not be mutated /// during the course of a conversion. public class ChineseConverter { - /// These constants define the ChineseConverter options. - public struct Options: OptionSet { - public let rawValue: Int + /// These constants define the ChineseConverter options. + public struct Options: OptionSet { + public let rawValue: Int - public init(rawValue: Int) { - self.rawValue = rawValue - } + public init(rawValue: Int) { + self.rawValue = rawValue + } - /// Convert to Traditional Chinese. (default) - public static let traditionalize = Options(rawValue: 1 << 0) + /// Convert to Traditional Chinese. (default) + public static let traditionalize = Options(rawValue: 1 << 0) - /// Convert to Simplified Chinese. - public static let simplify = Options(rawValue: 1 << 1) + /// Convert to Simplified Chinese. + public static let simplify = Options(rawValue: 1 << 1) - /// Use Taiwan standard. - public static let twStandard = Options(rawValue: 1 << 5) + /// Use Taiwan standard. + public static let twStandard = Options(rawValue: 1 << 5) - /// Use HongKong standard. - public static let hkStandard = Options(rawValue: 1 << 6) + /// Use HongKong standard. + public static let hkStandard = Options(rawValue: 1 << 6) - /// Cancel Taiwan standard. - public static let twStandardRev = Options(rawValue: 1 << 15) + /// Cancel Taiwan standard. + public static let twStandardRev = Options(rawValue: 1 << 15) - /// Cancel HongKong standard. - public static let hkStandardRev = Options(rawValue: 1 << 16) + /// Cancel HongKong standard. + public static let hkStandardRev = Options(rawValue: 1 << 16) - /// Taiwanese idiom conversion. - public static let twIdiom = Options(rawValue: 1 << 10) - } + /// Taiwanese idiom conversion. + public static let twIdiom = Options(rawValue: 1 << 10) + } - private let seg: ConversionDictionary - private let chain: [ConversionDictionary] + private let seg: ConversionDictionary + private let chain: [ConversionDictionary] - private let converter: CCConverterRef + private let converter: CCConverterRef - private init(loader: DictionaryLoader, options: Options) throws { - seg = try loader.segmentation(options: options) - chain = try loader.conversionChain(options: options) - var rawChain = chain.map(\.dict) - converter = CCConverterCreate("SwiftyOpenCC", seg.dict, &rawChain, rawChain.count) - } + private init(loader: DictionaryLoader, options: Options) throws { + seg = try loader.segmentation(options: options) + chain = try loader.conversionChain(options: options) + var rawChain = chain.map(\.dict) + converter = CCConverterCreate("SwiftyOpenCC", seg.dict, &rawChain, rawChain.count) + } - /// Returns an initialized `ChineseConverter` instance with the specified - /// conversion options. - /// - /// - Parameter options: The convert’s options. - /// - Throws: Throws `ConversionError` if failed. - public convenience init(options: Options) throws { - let loader = DictionaryLoader(bundle: .module) - try self.init(loader: loader, options: options) - } + /// Returns an initialized `ChineseConverter` instance with the specified + /// conversion options. + /// + /// - Parameter options: The convert’s options. + /// - Throws: Throws `ConversionError` if failed. + public convenience init(options: Options) throws { + let loader = DictionaryLoader(bundle: .module) + try self.init(loader: loader, options: options) + } - /// Return a converted string using the convert’s current option. - /// - /// - Parameter text: The string to convert. - /// - Returns: A converted string using the convert’s current option. - public func convert(_ text: String) -> String { - let stlStr = CCConverterCreateConvertedStringFromString(converter, text)! - defer { STLStringDestroy(stlStr) } - return String(utf8String: STLStringGetUTF8String(stlStr))! - } + /// Return a converted string using the convert’s current option. + /// + /// - Parameter text: The string to convert. + /// - Returns: A converted string using the convert’s current option. + public func convert(_ text: String) -> String { + let stlStr = CCConverterCreateConvertedStringFromString(converter, text)! + defer { STLStringDestroy(stlStr) } + return String(utf8String: STLStringGetUTF8String(stlStr))! + } } diff --git a/Packages/SwiftyOpenCC/Sources/OpenCC/ConversionDictionary.swift b/Packages/SwiftyOpenCC/Sources/OpenCC/ConversionDictionary.swift index e508948d..f056101c 100644 --- a/Packages/SwiftyOpenCC/Sources/OpenCC/ConversionDictionary.swift +++ b/Packages/SwiftyOpenCC/Sources/OpenCC/ConversionDictionary.swift @@ -9,21 +9,21 @@ import Foundation import copencc class ConversionDictionary { - let group: [ConversionDictionary] + let group: [ConversionDictionary] - let dict: CCDictRef + let dict: CCDictRef - init(path: String) throws { - guard let dict = CCDictCreateMarisaWithPath(path) else { - throw ConversionError(ccErrorno) - } - group = [] - self.dict = dict - } + init(path: String) throws { + guard let dict = CCDictCreateMarisaWithPath(path) else { + throw ConversionError(ccErrorno) + } + group = [] + self.dict = dict + } - init(group: [ConversionDictionary]) { - var rawGroup = group.map(\.dict) - self.group = group - dict = CCDictCreateWithGroup(&rawGroup, rawGroup.count) - } + init(group: [ConversionDictionary]) { + var rawGroup = group.map(\.dict) + self.group = group + dict = CCDictCreateWithGroup(&rawGroup, rawGroup.count) + } } diff --git a/Packages/SwiftyOpenCC/Sources/OpenCC/ConversionError.swift b/Packages/SwiftyOpenCC/Sources/OpenCC/ConversionError.swift index e84edb0b..2dabfd66 100644 --- a/Packages/SwiftyOpenCC/Sources/OpenCC/ConversionError.swift +++ b/Packages/SwiftyOpenCC/Sources/OpenCC/ConversionError.swift @@ -9,28 +9,28 @@ import Foundation import copencc public enum ConversionError: Error { - case fileNotFound + case fileNotFound - case invalidFormat + case invalidFormat - case invalidTextDictionary + case invalidTextDictionary - case invalidUTF8 + case invalidUTF8 - case unknown + case unknown - init(_ code: CCErrorCode) { - switch code { - case .fileNotFound: - self = .fileNotFound - case .invalidFormat: - self = .invalidFormat - case .invalidTextDictionary: - self = .invalidTextDictionary - case .invalidUTF8: - self = .invalidUTF8 - case .unknown, _: - self = .unknown - } - } + init(_ code: CCErrorCode) { + switch code { + case .fileNotFound: + self = .fileNotFound + case .invalidFormat: + self = .invalidFormat + case .invalidTextDictionary: + self = .invalidTextDictionary + case .invalidUTF8: + self = .invalidUTF8 + case .unknown, _: + self = .unknown + } + } } diff --git a/Packages/SwiftyOpenCC/Sources/OpenCC/DictionaryLoader.swift b/Packages/SwiftyOpenCC/Sources/OpenCC/DictionaryLoader.swift index bc63693d..7f8468d5 100644 --- a/Packages/SwiftyOpenCC/Sources/OpenCC/DictionaryLoader.swift +++ b/Packages/SwiftyOpenCC/Sources/OpenCC/DictionaryLoader.swift @@ -9,49 +9,49 @@ import Foundation import copencc extension ChineseConverter { - struct DictionaryLoader { - private static let subdirectory = "Dictionary" - private static let dictCache = WeakValueCache() + struct DictionaryLoader { + private static let subdirectory = "Dictionary" + private static let dictCache = WeakValueCache() - private let bundle: Bundle + private let bundle: Bundle - init(bundle: Bundle) { - self.bundle = bundle - } + init(bundle: Bundle) { + self.bundle = bundle + } - func dict(_ name: ChineseConverter.DictionaryName) throws -> ConversionDictionary { - guard - let path = bundle.path( - forResource: name.description, ofType: "ocd2", - inDirectory: DictionaryLoader.subdirectory - ) - else { - throw ConversionError.fileNotFound - } - return try DictionaryLoader.dictCache.value(for: path) { - try ConversionDictionary(path: path) - } - } - } + func dict(_ name: ChineseConverter.DictionaryName) throws -> ConversionDictionary { + guard + let path = bundle.path( + forResource: name.description, ofType: "ocd2", + inDirectory: DictionaryLoader.subdirectory + ) + else { + throw ConversionError.fileNotFound + } + return try DictionaryLoader.dictCache.value(for: path) { + try ConversionDictionary(path: path) + } + } + } } extension ChineseConverter.DictionaryLoader { - func segmentation(options: ChineseConverter.Options) throws -> ConversionDictionary { - let dictName = options.segmentationDictName - return try dict(dictName) - } + func segmentation(options: ChineseConverter.Options) throws -> ConversionDictionary { + let dictName = options.segmentationDictName + return try dict(dictName) + } - func conversionChain(options: ChineseConverter.Options) throws -> [ConversionDictionary] { - try options.conversionChain.compactMap { names in - switch names.count { - case 0: - return nil - case 1: - return try dict(names.first!) - case _: - let dicts = try names.map(dict) - return ConversionDictionary(group: dicts) - } - } - } + func conversionChain(options: ChineseConverter.Options) throws -> [ConversionDictionary] { + try options.conversionChain.compactMap { names in + switch names.count { + case 0: + return nil + case 1: + return try dict(names.first!) + case _: + let dicts = try names.map(dict) + return ConversionDictionary(group: dicts) + } + } + } } diff --git a/Packages/SwiftyOpenCC/Sources/OpenCC/DictionaryName.swift b/Packages/SwiftyOpenCC/Sources/OpenCC/DictionaryName.swift index a1768b7e..11e7520f 100644 --- a/Packages/SwiftyOpenCC/Sources/OpenCC/DictionaryName.swift +++ b/Packages/SwiftyOpenCC/Sources/OpenCC/DictionaryName.swift @@ -8,96 +8,96 @@ import Foundation extension ChineseConverter { - enum DictionaryName: CustomStringConvertible { - case hkVariants - case hkVariantsRev - case hkVariantsRevPhrases - case jpVariants - case stCharacters - case stPhrases - case tsCharacters - case tsPhrases - case twPhrases - case twPhrasesRev - case twVariants - case twVariantsRev - case twVariantsRevPhrases + enum DictionaryName: CustomStringConvertible { + case hkVariants + case hkVariantsRev + case hkVariantsRevPhrases + case jpVariants + case stCharacters + case stPhrases + case tsCharacters + case tsPhrases + case twPhrases + case twPhrasesRev + case twVariants + case twVariantsRev + case twVariantsRevPhrases - var description: String { - switch self { - case .hkVariants: return "HKVariants" - case .hkVariantsRev: return "HKVariantsRev" - case .hkVariantsRevPhrases: return "HKVariantsRevPhrases" - case .jpVariants: return "JPVariants" - case .stCharacters: return "STCharacters" - case .stPhrases: return "STPhrases" - case .tsCharacters: return "TSCharacters" - case .tsPhrases: return "TSPhrases" - case .twPhrases: return "TWPhrases" - case .twPhrasesRev: return "TWPhrasesRev" - case .twVariants: return "TWVariants" - case .twVariantsRev: return "TWVariantsRev" - case .twVariantsRevPhrases: return "TWVariantsRevPhrases" - } - } - } + var description: String { + switch self { + case .hkVariants: return "HKVariants" + case .hkVariantsRev: return "HKVariantsRev" + case .hkVariantsRevPhrases: return "HKVariantsRevPhrases" + case .jpVariants: return "JPVariants" + case .stCharacters: return "STCharacters" + case .stPhrases: return "STPhrases" + case .tsCharacters: return "TSCharacters" + case .tsPhrases: return "TSPhrases" + case .twPhrases: return "TWPhrases" + case .twPhrasesRev: return "TWPhrasesRev" + case .twVariants: return "TWVariants" + case .twVariantsRev: return "TWVariantsRev" + case .twVariantsRevPhrases: return "TWVariantsRevPhrases" + } + } + } } extension ChineseConverter.Options { - var segmentationDictName: ChineseConverter.DictionaryName { - if contains(.traditionalize) { - return .stPhrases - } else if contains(.simplify) { - return .tsPhrases - } else if contains(.hkStandard) { - return .hkVariants - } else if contains(.twStandard) { - return .twVariants - } else if contains(.hkStandardRev) { - return .hkVariantsRev - } else if contains(.twStandardRev) { - return .twVariantsRev - } else { - return .stPhrases - } - } + var segmentationDictName: ChineseConverter.DictionaryName { + if contains(.traditionalize) { + return .stPhrases + } else if contains(.simplify) { + return .tsPhrases + } else if contains(.hkStandard) { + return .hkVariants + } else if contains(.twStandard) { + return .twVariants + } else if contains(.hkStandardRev) { + return .hkVariantsRev + } else if contains(.twStandardRev) { + return .twVariantsRev + } else { + return .stPhrases + } + } - var conversionChain: [[ChineseConverter.DictionaryName]] { - var result: [[ChineseConverter.DictionaryName]] = [] - if contains(.traditionalize) { - result.append([.stPhrases, .stCharacters]) - if contains(.twIdiom) { - result.append([.twPhrases]) - } - if contains(.hkStandard) { - result.append([.hkVariants]) - } else if contains(.twStandard) { - result.append([.twVariants]) - } - } else if contains(.simplify) { - if contains(.hkStandard) { - result.append([.hkVariantsRevPhrases, .hkVariantsRev]) - } else if contains(.twStandard) { - result.append([.twVariantsRevPhrases, .twVariantsRev]) - } - if contains(.twIdiom) { - result.append([.twPhrasesRev]) - } - result.append([.tsPhrases, .tsCharacters]) - } else { - if contains(.hkStandard) { - result.append([.hkVariants]) - } else if contains(.twStandard) { - result.append([.twVariants]) - } else if contains(.hkStandardRev) { - result.append([.hkVariantsRev]) - } else if contains(.twStandardRev) { - result.append([.twVariantsRev]) - } - } - if result.isEmpty { - return [[.stPhrases, .stCharacters]] - } - return result - } + var conversionChain: [[ChineseConverter.DictionaryName]] { + var result: [[ChineseConverter.DictionaryName]] = [] + if contains(.traditionalize) { + result.append([.stPhrases, .stCharacters]) + if contains(.twIdiom) { + result.append([.twPhrases]) + } + if contains(.hkStandard) { + result.append([.hkVariants]) + } else if contains(.twStandard) { + result.append([.twVariants]) + } + } else if contains(.simplify) { + if contains(.hkStandard) { + result.append([.hkVariantsRevPhrases, .hkVariantsRev]) + } else if contains(.twStandard) { + result.append([.twVariantsRevPhrases, .twVariantsRev]) + } + if contains(.twIdiom) { + result.append([.twPhrasesRev]) + } + result.append([.tsPhrases, .tsCharacters]) + } else { + if contains(.hkStandard) { + result.append([.hkVariants]) + } else if contains(.twStandard) { + result.append([.twVariants]) + } else if contains(.hkStandardRev) { + result.append([.hkVariantsRev]) + } else if contains(.twStandardRev) { + result.append([.twVariantsRev]) + } + } + if result.isEmpty { + return [[.stPhrases, .stCharacters]] + } + return result + } } diff --git a/Packages/SwiftyOpenCC/Sources/OpenCC/WeakValueCache.swift b/Packages/SwiftyOpenCC/Sources/OpenCC/WeakValueCache.swift index 489e2c55..35b32b7a 100644 --- a/Packages/SwiftyOpenCC/Sources/OpenCC/WeakValueCache.swift +++ b/Packages/SwiftyOpenCC/Sources/OpenCC/WeakValueCache.swift @@ -8,33 +8,33 @@ import Foundation class WeakBox { - private(set) weak var value: Value? + private(set) weak var value: Value? - init(_ value: Value) { - self.value = value - } + init(_ value: Value) { + self.value = value + } } class WeakValueCache { - private var storage: [Key: WeakBox] = [:] + private var storage: [Key: WeakBox] = [:] - private var lock = NSLock() + private var lock = NSLock() - func value(for key: Key) -> Value? { - storage[key]?.value - } + func value(for key: Key) -> Value? { + storage[key]?.value + } - func value(for key: Key, make: () throws -> Value) rethrows -> Value { - if let value = storage[key]?.value { - return value - } - lock.lock() - defer { lock.unlock() } - if let value = storage[key]?.value { - return value - } - let value = try make() - storage[key] = WeakBox(value) - return value - } + func value(for key: Key, make: () throws -> Value) rethrows -> Value { + if let value = storage[key]?.value { + return value + } + lock.lock() + defer { lock.unlock() } + if let value = storage[key]?.value { + return value + } + let value = try make() + storage[key] = WeakBox(value) + return value + } } diff --git a/Packages/SwiftyOpenCC/Tests/OpenCCTests/OpenCCTests.swift b/Packages/SwiftyOpenCC/Tests/OpenCCTests/OpenCCTests.swift index 93f3670c..f5052277 100644 --- a/Packages/SwiftyOpenCC/Tests/OpenCCTests/OpenCCTests.swift +++ b/Packages/SwiftyOpenCC/Tests/OpenCCTests/OpenCCTests.swift @@ -3,66 +3,66 @@ import XCTest @testable import OpenCC let testCases: [(String, ChineseConverter.Options)] = [ - ("s2t", [.traditionalize]), - ("t2s", [.simplify]), - ("s2hk", [.traditionalize, .hkStandard]), - ("hk2s", [.simplify, .hkStandard]), - ("s2tw", [.traditionalize, .twStandard]), - ("tw2s", [.simplify, .twStandard]), - ("s2twp", [.traditionalize, .twStandard, .twIdiom]), - ("tw2sp", [.simplify, .twStandard, .twIdiom]), + ("s2t", [.traditionalize]), + ("t2s", [.simplify]), + ("s2hk", [.traditionalize, .hkStandard]), + ("hk2s", [.simplify, .hkStandard]), + ("s2tw", [.traditionalize, .twStandard]), + ("tw2s", [.simplify, .twStandard]), + ("s2twp", [.traditionalize, .twStandard, .twIdiom]), + ("tw2sp", [.simplify, .twStandard, .twIdiom]), ] class OpenCCTests: XCTestCase { - func converter(option: ChineseConverter.Options) throws -> ChineseConverter { - try ChineseConverter(options: option) - } + func converter(option: ChineseConverter.Options) throws -> ChineseConverter { + try ChineseConverter(options: option) + } - func testConversion() throws { - func testCase(name: String, ext: String) -> String { - let url = Bundle.module.url( - forResource: name, withExtension: ext, subdirectory: "testcases" - )! - return try! String(contentsOf: url) - } - for (name, opt) in testCases { - let coverter = try ChineseConverter(options: opt) - let input = testCase(name: name, ext: "in") - let converted = coverter.convert(input) - let output = testCase(name: name, ext: "ans") - XCTAssertEqual(converted, output, "Conversion \(name) fails") - } - } + func testConversion() throws { + func testCase(name: String, ext: String) -> String { + let url = Bundle.module.url( + forResource: name, withExtension: ext, subdirectory: "testcases" + )! + return try! String(contentsOf: url) + } + for (name, opt) in testCases { + let coverter = try ChineseConverter(options: opt) + let input = testCase(name: name, ext: "in") + let converted = coverter.convert(input) + let output = testCase(name: name, ext: "ans") + XCTAssertEqual(converted, output, "Conversion \(name) fails") + } + } - func testConverterCreationPerformance() { - let options: ChineseConverter.Options = [.traditionalize, .twStandard, .twIdiom] - measure { - for _ in 0..<10 { - _ = try! ChineseConverter(options: options) - } - } - } + func testConverterCreationPerformance() { + let options: ChineseConverter.Options = [.traditionalize, .twStandard, .twIdiom] + measure { + for _ in 0..<10 { + _ = try! ChineseConverter(options: options) + } + } + } - func testDictionaryCache() { - let options: ChineseConverter.Options = [.traditionalize, .twStandard, .twIdiom] - let holder = try! ChineseConverter(options: options) - measure { - for _ in 0..<1000 { - _ = try! ChineseConverter(options: options) - } - } - _ = holder.convert("foo") - } + func testDictionaryCache() { + let options: ChineseConverter.Options = [.traditionalize, .twStandard, .twIdiom] + let holder = try! ChineseConverter(options: options) + measure { + for _ in 0..<1000 { + _ = try! ChineseConverter(options: options) + } + } + _ = holder.convert("foo") + } - func testConversionPerformance() throws { - let cov = try converter(option: [.traditionalize, .twStandard, .twIdiom]) - let url = Bundle.module.url( - forResource: "zuozhuan", withExtension: "txt", subdirectory: "benchmark" - )! - // 1.9 MB, 624k word - let str = try String(contentsOf: url) - measure { - _ = cov.convert(str) - } - } + func testConversionPerformance() throws { + let cov = try converter(option: [.traditionalize, .twStandard, .twIdiom]) + let url = Bundle.module.url( + forResource: "zuozhuan", withExtension: "txt", subdirectory: "benchmark" + )! + // 1.9 MB, 624k word + let str = try String(contentsOf: url) + measure { + _ = cov.convert(str) + } + } } diff --git a/Source/3rdParty/LineReader/LineReader.swift b/Source/3rdParty/LineReader/LineReader.swift new file mode 100644 index 00000000..01a91ea2 --- /dev/null +++ b/Source/3rdParty/LineReader/LineReader.swift @@ -0,0 +1,69 @@ +// Copyright (c) 2019 and onwards Robert Muckle-Jones (Apache 2.0 License). + +import Foundation + +public class LineReader { + let encoding: String.Encoding + let chunkSize: Int + var fileHandle: FileHandle + let delimData: Data + var buffer: Data + var atEof: Bool + + public init( + file: FileHandle, encoding: String.Encoding = .utf8, + chunkSize: Int = 4096 + ) throws { + let fileHandle = file + self.encoding = encoding + self.chunkSize = chunkSize + self.fileHandle = fileHandle + delimData = "\n".data(using: encoding)! + buffer = Data(capacity: chunkSize) + atEof = false + } + + /// Return next line, or nil on EOF. + public func nextLine() -> String? { + // Read data chunks from file until a line delimiter is found: + while !atEof { + // get a data from the buffer up to the next delimiter + if let range = buffer.range(of: delimData) { + // convert data to a string + let line = String(data: buffer.subdata(in: 0.. AnyIterator { + AnyIterator { + self.nextLine() + } + } +} diff --git a/Source/Modules/LangModelRelated/SubLanguageModels/InstantiatedModels/UserSymbolLM.h b/Source/3rdParty/OVMandarin/Composer.hh similarity index 77% rename from Source/Modules/LangModelRelated/SubLanguageModels/InstantiatedModels/UserSymbolLM.h rename to Source/3rdParty/OVMandarin/Composer.hh index 7f37c3ac..d620b766 100644 --- a/Source/Modules/LangModelRelated/SubLanguageModels/InstantiatedModels/UserSymbolLM.h +++ b/Source/3rdParty/OVMandarin/Composer.hh @@ -24,31 +24,20 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#ifndef USERSYMBOLLM_H -#define USERSYMBOLLM_H +#import -#include "LanguageModel.h" -#include "UserPhrasesLM.h" -#include -#include -#include +NS_ASSUME_NONNULL_BEGIN -namespace vChewing -{ +@interface Composer : NSObject ++ (BOOL)chkKeyValidity:(UniChar)charCode; ++ (BOOL)isBufferEmpty; ++ (void)clearBuffer; ++ (void)combineReadingKey:(UniChar)charCode; ++ (BOOL)checkWhetherToneMarkerConfirms; ++ (NSString *)getSyllableComposition; ++ (void)doBackSpaceToBuffer; ++ (NSString *)getComposition; ++ (void)ensureParser; +@end -class UserSymbolLM : public UserPhrasesLM -{ - public: - bool allowConsolidation() override - { - return true; - } - float overridedValue() override - { - return -12.0; - } -}; - -} // namespace vChewing - -#endif +NS_ASSUME_NONNULL_END diff --git a/Source/3rdParty/OVMandarin/Composer.mm b/Source/3rdParty/OVMandarin/Composer.mm new file mode 100644 index 00000000..05bf391b --- /dev/null +++ b/Source/3rdParty/OVMandarin/Composer.mm @@ -0,0 +1,116 @@ +// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). +/* +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#import "Composer.hh" +#import "Mandarin.h" +#import "vChewing-Swift.h" + +static Mandarin::BopomofoReadingBuffer *PhoneticBuffer; + +@implementation Composer + ++ (BOOL)chkKeyValidity:(UniChar)charCode +{ + return PhoneticBuffer->isValidKey((char)charCode); +} + ++ (BOOL)isBufferEmpty +{ + return PhoneticBuffer->isEmpty(); +} + ++ (void)clearBuffer +{ + PhoneticBuffer->clear(); +} + ++ (void)combineReadingKey:(UniChar)charCode +{ + PhoneticBuffer->combineKey((char)charCode); +} + ++ (BOOL)checkWhetherToneMarkerConfirms +{ + return PhoneticBuffer->hasToneMarker(); +} + ++ (NSString *)getSyllableComposition +{ + return [NSString stringWithUTF8String:PhoneticBuffer->syllable().composedString().c_str()]; +} + ++ (void)doBackSpaceToBuffer +{ + PhoneticBuffer->backspace(); +} + ++ (NSString *)getComposition +{ + return [NSString stringWithUTF8String:PhoneticBuffer->composedString().c_str()]; +} + ++ (void)ensureParser +{ + if (PhoneticBuffer) + { + switch (mgrPrefs.mandarinParser) + { + case MandarinParserOfStandard: + PhoneticBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::StandardLayout()); + break; + case MandarinParserOfEten: + PhoneticBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::ETenLayout()); + break; + case MandarinParserOfHsu: + PhoneticBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::HsuLayout()); + break; + case MandarinParserOfEen26: + PhoneticBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::ETen26Layout()); + break; + case MandarinParserOfIBM: + PhoneticBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::IBMLayout()); + break; + case MandarinParserOfMiTAC: + PhoneticBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::MiTACLayout()); + break; + case MandarinParserOfFakeSeigyou: + PhoneticBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::FakeSeigyouLayout()); + break; + case MandarinParserOfHanyuPinyin: + PhoneticBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::HanyuPinyinLayout()); + break; + default: + PhoneticBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::StandardLayout()); + mgrPrefs.mandarinParser = MandarinParserOfStandard; + } + } + else + { + PhoneticBuffer = new Mandarin::BopomofoReadingBuffer(Mandarin::BopomofoKeyboardLayout::StandardLayout()); + } +} + +@end diff --git a/Source/3rdParty/OpenCCBridge/OpenCCBridge.swift b/Source/3rdParty/OpenCCBridge/OpenCCBridge.swift index e7133cd4..bf5d9f1c 100644 --- a/Source/3rdParty/OpenCCBridge/OpenCCBridge.swift +++ b/Source/3rdParty/OpenCCBridge/OpenCCBridge.swift @@ -32,28 +32,28 @@ import OpenCC /// Since SwiftyOpenCC only provide Swift classes, we create an NSObject subclass /// in Swift in order to bridge the Swift classes into our Objective-C++ project. public class OpenCCBridge: NSObject { - private static let shared = OpenCCBridge() - private var simplify: ChineseConverter? - private var traditionalize: ChineseConverter? + private static let shared = OpenCCBridge() + private var simplify: ChineseConverter? + private var traditionalize: ChineseConverter? - override private init() { - try? simplify = ChineseConverter(options: .simplify) - try? traditionalize = ChineseConverter(options: [.traditionalize, .twStandard]) - super.init() - } + override private init() { + try? simplify = ChineseConverter(options: .simplify) + try? traditionalize = ChineseConverter(options: [.traditionalize, .twStandard]) + super.init() + } - /// CrossConvert. - /// - /// - Parameter string: Text in Original Script. - /// - Returns: Text converted to Different Script. - @objc public static func crossConvert(_ string: String) -> String? { - switch ctlInputMethod.currentKeyHandler.inputMode { - case InputMode.imeModeCHS: - return shared.traditionalize?.convert(string) - case InputMode.imeModeCHT: - return shared.simplify?.convert(string) - default: - return string - } - } + /// CrossConvert. + /// + /// - Parameter string: Text in Original Script. + /// - Returns: Text converted to Different Script. + public static func crossConvert(_ string: String) -> String? { + switch ctlInputMethod.currentKeyHandler.inputMode { + case InputMode.imeModeCHS: + return shared.traditionalize?.convert(string) + case InputMode.imeModeCHT: + return shared.simplify?.convert(string) + default: + return string + } + } } diff --git a/Source/3rdParty/SindreSorhus/Preferences/Container.swift b/Source/3rdParty/SindreSorhus/Preferences/Container.swift index acf4a546..115f7e58 100755 --- a/Source/3rdParty/SindreSorhus/Preferences/Container.swift +++ b/Source/3rdParty/SindreSorhus/Preferences/Container.swift @@ -22,82 +22,82 @@ import SwiftUI @available(macOS 10.15, *) extension Preferences { - /** - Function builder for `Preferences` components used in order to restrict types of child views to be of type `Section`. - */ - @resultBuilder - public enum SectionBuilder { - public static func buildBlock(_ sections: Section...) -> [Section] { - sections - } - } + /** + Function builder for `Preferences` components used in order to restrict types of child views to be of type `Section`. + */ + @resultBuilder + public enum SectionBuilder { + public static func buildBlock(_ sections: Section...) -> [Section] { + sections + } + } - /** - A view which holds `Preferences.Section` views and does all the alignment magic similar to `NSGridView` from AppKit. - */ - public struct Container: View { - private let sectionBuilder: () -> [Section] - private let contentWidth: Double - private let minimumLabelWidth: Double - @State private var maximumLabelWidth = 0.0 + /** + A view which holds `Preferences.Section` views and does all the alignment magic similar to `NSGridView` from AppKit. + */ + public struct Container: View { + private let sectionBuilder: () -> [Section] + private let contentWidth: Double + private let minimumLabelWidth: Double + @State private var maximumLabelWidth = 0.0 - /** - Creates an instance of container component, which handles layout of stacked `Preferences.Section` views. + /** + Creates an instance of container component, which handles layout of stacked `Preferences.Section` views. - Custom alignment requires content width to be specified beforehand. + Custom alignment requires content width to be specified beforehand. - - Parameters: - - contentWidth: A fixed width of the container's content (excluding paddings). - - minimumLabelWidth: A minimum width for labels within this container. By default, it will fit to the largest label. - - builder: A view builder that creates `Preferences.Section`'s of this container. - */ - public init( - contentWidth: Double, - minimumLabelWidth: Double = 0, - @SectionBuilder builder: @escaping () -> [Section] - ) { - sectionBuilder = builder - self.contentWidth = contentWidth - self.minimumLabelWidth = minimumLabelWidth - } + - Parameters: + - contentWidth: A fixed width of the container's content (excluding paddings). + - minimumLabelWidth: A minimum width for labels within this container. By default, it will fit to the largest label. + - builder: A view builder that creates `Preferences.Section`'s of this container. + */ + public init( + contentWidth: Double, + minimumLabelWidth: Double = 0, + @SectionBuilder builder: @escaping () -> [Section] + ) { + sectionBuilder = builder + self.contentWidth = contentWidth + self.minimumLabelWidth = minimumLabelWidth + } - public var body: some View { - let sections = sectionBuilder() + public var body: some View { + let sections = sectionBuilder() - return VStack(alignment: .preferenceSectionLabel) { - ForEach(0.. some View { - sections[index] - if index != sections.count - 1, sections[index].bottomDivider { - Divider() - // Strangely doesn't work without width being specified. Probably because of custom alignment. - .frame(width: CGFloat(contentWidth), height: 20) - .alignmentGuide(.preferenceSectionLabel) { - $0[.leading] + CGFloat(max(minimumLabelWidth, maximumLabelWidth)) - } - } - } - } + @ViewBuilder + private func viewForSection(_ sections: [Section], index: Int) -> some View { + sections[index] + if index != sections.count - 1, sections[index].bottomDivider { + Divider() + // Strangely doesn't work without width being specified. Probably because of custom alignment. + .frame(width: CGFloat(contentWidth), height: 20) + .alignmentGuide(.preferenceSectionLabel) { + $0[.leading] + CGFloat(max(minimumLabelWidth, maximumLabelWidth)) + } + } + } + } } /// Extension with custom alignment guide for section title labels. @available(macOS 10.15, *) extension HorizontalAlignment { - private enum PreferenceSectionLabelAlignment: AlignmentID { - static func defaultValue(in context: ViewDimensions) -> CGFloat { - context[HorizontalAlignment.leading] - } - } + private enum PreferenceSectionLabelAlignment: AlignmentID { + static func defaultValue(in context: ViewDimensions) -> CGFloat { + context[HorizontalAlignment.leading] + } + } - static let preferenceSectionLabel = HorizontalAlignment(PreferenceSectionLabelAlignment.self) + static let preferenceSectionLabel = HorizontalAlignment(PreferenceSectionLabelAlignment.self) } diff --git a/Source/3rdParty/SindreSorhus/Preferences/Localization.swift b/Source/3rdParty/SindreSorhus/Preferences/Localization.swift index 9d19e190..de0c60cc 100755 --- a/Source/3rdParty/SindreSorhus/Preferences/Localization.swift +++ b/Source/3rdParty/SindreSorhus/Preferences/Localization.swift @@ -21,135 +21,135 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import Foundation struct Localization { - enum Identifier { - case preferences - case preferencesEllipsized - } + enum Identifier { + case preferences + case preferencesEllipsized + } - private static let localizedStrings: [Identifier: [String: String]] = [ - .preferences: [ - "ar": "تفضيلات", - "ca": "Preferències", - "cs": "Předvolby", - "da": "Indstillinger", - "de": "Einstellungen", - "el": "Προτιμήσεις", - "en": "Preferences", - "en-AU": "Preferences", - "en-GB": "Preferences", - "es": "Preferencias", - "es-419": "Preferencias", - "fi": "Asetukset", - "fr": "Préférences", - "fr-CA": "Préférences", - "he": "העדפות", - "hi": "प्राथमिकता", - "hr": "Postavke", - "hu": "Beállítások", - "id": "Preferensi", - "it": "Preferenze", - "ja": "環境設定", - "ko": "환경설정", - "ms": "Keutamaan", - "nl": "Voorkeuren", - "no": "Valg", - "pl": "Preferencje", - "pt": "Preferências", - "pt-PT": "Preferências", - "ro": "Preferințe", - "ru": "Настройки", - "sk": "Nastavenia", - "sv": "Inställningar", - "th": "การตั้งค่า", - "tr": "Tercihler", - "uk": "Параметри", - "vi": "Tùy chọn", - "zh-CN": "偏好设置", - "zh-HK": "偏好設定", - "zh-TW": "偏好設定", - ], - .preferencesEllipsized: [ - "ar": "تفضيلات…", - "ca": "Preferències…", - "cs": "Předvolby…", - "da": "Indstillinger…", - "de": "Einstellungen…", - "el": "Προτιμήσεις…", - "en": "Preferences…", - "en-AU": "Preferences…", - "en-GB": "Preferences…", - "es": "Preferencias…", - "es-419": "Preferencias…", - "fi": "Asetukset…", - "fr": "Préférences…", - "fr-CA": "Préférences…", - "he": "העדפות…", - "hi": "प्राथमिकता…", - "hr": "Postavke…", - "hu": "Beállítások…", - "id": "Preferensi…", - "it": "Preferenze…", - "ja": "環境設定…", - "ko": "환경설정...", - "ms": "Keutamaan…", - "nl": "Voorkeuren…", - "no": "Valg…", - "pl": "Preferencje…", - "pt": "Preferências…", - "pt-PT": "Preferências…", - "ro": "Preferințe…", - "ru": "Настройки…", - "sk": "Nastavenia…", - "sv": "Inställningar…", - "th": "การตั้งค่า…", - "tr": "Tercihler…", - "uk": "Параметри…", - "vi": "Tùy chọn…", - "zh-CN": "偏好设置…", - "zh-HK": "偏好設定⋯", - "zh-TW": "偏好設定⋯", - ], - ] + private static let localizedStrings: [Identifier: [String: String]] = [ + .preferences: [ + "ar": "تفضيلات", + "ca": "Preferències", + "cs": "Předvolby", + "da": "Indstillinger", + "de": "Einstellungen", + "el": "Προτιμήσεις", + "en": "Preferences", + "en-AU": "Preferences", + "en-GB": "Preferences", + "es": "Preferencias", + "es-419": "Preferencias", + "fi": "Asetukset", + "fr": "Préférences", + "fr-CA": "Préférences", + "he": "העדפות", + "hi": "प्राथमिकता", + "hr": "Postavke", + "hu": "Beállítások", + "id": "Preferensi", + "it": "Preferenze", + "ja": "環境設定", + "ko": "환경설정", + "ms": "Keutamaan", + "nl": "Voorkeuren", + "no": "Valg", + "pl": "Preferencje", + "pt": "Preferências", + "pt-PT": "Preferências", + "ro": "Preferințe", + "ru": "Настройки", + "sk": "Nastavenia", + "sv": "Inställningar", + "th": "การตั้งค่า", + "tr": "Tercihler", + "uk": "Параметри", + "vi": "Tùy chọn", + "zh-CN": "偏好设置", + "zh-HK": "偏好設定", + "zh-TW": "偏好設定", + ], + .preferencesEllipsized: [ + "ar": "تفضيلات…", + "ca": "Preferències…", + "cs": "Předvolby…", + "da": "Indstillinger…", + "de": "Einstellungen…", + "el": "Προτιμήσεις…", + "en": "Preferences…", + "en-AU": "Preferences…", + "en-GB": "Preferences…", + "es": "Preferencias…", + "es-419": "Preferencias…", + "fi": "Asetukset…", + "fr": "Préférences…", + "fr-CA": "Préférences…", + "he": "העדפות…", + "hi": "प्राथमिकता…", + "hr": "Postavke…", + "hu": "Beállítások…", + "id": "Preferensi…", + "it": "Preferenze…", + "ja": "環境設定…", + "ko": "환경설정...", + "ms": "Keutamaan…", + "nl": "Voorkeuren…", + "no": "Valg…", + "pl": "Preferencje…", + "pt": "Preferências…", + "pt-PT": "Preferências…", + "ro": "Preferințe…", + "ru": "Настройки…", + "sk": "Nastavenia…", + "sv": "Inställningar…", + "th": "การตั้งค่า…", + "tr": "Tercihler…", + "uk": "Параметри…", + "vi": "Tùy chọn…", + "zh-CN": "偏好设置…", + "zh-HK": "偏好設定⋯", + "zh-TW": "偏好設定⋯", + ], + ] - /** - Returns the localized version of the given string. + /** + Returns the localized version of the given string. - - Parameter identifier: Identifier of the string to localize. + - Parameter identifier: Identifier of the string to localize. - - Note: If the system's locale can't be determined, the English localization of the string will be returned. - */ - static subscript(identifier: Identifier) -> String { - // Force-unwrapped since all of the involved code is under our control. - let localizedDict = Localization.localizedStrings[identifier]! - let defaultLocalizedString = localizedDict["en"]! + - Note: If the system's locale can't be determined, the English localization of the string will be returned. + */ + static subscript(identifier: Identifier) -> String { + // Force-unwrapped since all of the involved code is under our control. + let localizedDict = Localization.localizedStrings[identifier]! + let defaultLocalizedString = localizedDict["en"]! - // Iterate through all user-preferred languages until we find one that has a valid language code. - let preferredLocale = - Locale.preferredLanguages - .lazy - .map { Locale(identifier: $0) } - .first { $0.languageCode != nil } - ?? .current + // Iterate through all user-preferred languages until we find one that has a valid language code. + let preferredLocale = + Locale.preferredLanguages + .lazy + .map { Locale(identifier: $0) } + .first { $0.languageCode != nil } + ?? .current - guard let languageCode = preferredLocale.languageCode else { - return defaultLocalizedString - } + guard let languageCode = preferredLocale.languageCode else { + return defaultLocalizedString + } - // Chinese is the only language where different region codes result in different translations. - if languageCode == "zh" { - let regionCode = preferredLocale.regionCode ?? "" - if regionCode == "HK" || regionCode == "TW" { - return localizedDict["\(languageCode)-\(regionCode)"]! - } else { - // Fall back to "regular" zh-CN if neither the HK or TW region codes are found. - return localizedDict["\(languageCode)-CN"]! - } - } else { - if let localizedString = localizedDict[languageCode] { - return localizedString - } - } + // Chinese is the only language where different region codes result in different translations. + if languageCode == "zh" { + let regionCode = preferredLocale.regionCode ?? "" + if regionCode == "HK" || regionCode == "TW" { + return localizedDict["\(languageCode)-\(regionCode)"]! + } else { + // Fall back to "regular" zh-CN if neither the HK or TW region codes are found. + return localizedDict["\(languageCode)-CN"]! + } + } else { + if let localizedString = localizedDict[languageCode] { + return localizedString + } + } - return defaultLocalizedString - } + return defaultLocalizedString + } } diff --git a/Source/3rdParty/SindreSorhus/Preferences/Pane.swift b/Source/3rdParty/SindreSorhus/Preferences/Pane.swift index 8bff9f68..21b83af3 100755 --- a/Source/3rdParty/SindreSorhus/Preferences/Pane.swift +++ b/Source/3rdParty/SindreSorhus/Preferences/Pane.swift @@ -24,89 +24,89 @@ import SwiftUI /// /// Acts as type-eraser for `Preferences.Pane`. public protocol PreferencePaneConvertible { - /** - Convert `self` to equivalent `PreferencePane`. - */ - func asPreferencePane() -> PreferencePane + /** + Convert `self` to equivalent `PreferencePane`. + */ + func asPreferencePane() -> PreferencePane } @available(macOS 10.15, *) extension Preferences { - /** - Create a SwiftUI-based preference pane. + /** + Create a SwiftUI-based preference pane. - SwiftUI equivalent of the `PreferencePane` protocol. - */ - public struct Pane: View, PreferencePaneConvertible { - let identifier: PaneIdentifier - let title: String - let toolbarIcon: NSImage - let content: Content + SwiftUI equivalent of the `PreferencePane` protocol. + */ + public struct Pane: View, PreferencePaneConvertible { + let identifier: PaneIdentifier + let title: String + let toolbarIcon: NSImage + let content: Content - public init( - identifier: PaneIdentifier, - title: String, - toolbarIcon: NSImage, - contentView: () -> Content - ) { - self.identifier = identifier - self.title = title - self.toolbarIcon = toolbarIcon - content = contentView() - } + public init( + identifier: PaneIdentifier, + title: String, + toolbarIcon: NSImage, + contentView: () -> Content + ) { + self.identifier = identifier + self.title = title + self.toolbarIcon = toolbarIcon + content = contentView() + } - public var body: some View { content } + public var body: some View { content } - public func asPreferencePane() -> PreferencePane { - PaneHostingController(pane: self) - } - } + public func asPreferencePane() -> PreferencePane { + PaneHostingController(pane: self) + } + } - /** - Hosting controller enabling `Preferences.Pane` to be used alongside AppKit `NSViewController`'s. - */ - public final class PaneHostingController: NSHostingController, PreferencePane { - public let preferencePaneIdentifier: PaneIdentifier - public let preferencePaneTitle: String - public let toolbarItemIcon: NSImage + /** + Hosting controller enabling `Preferences.Pane` to be used alongside AppKit `NSViewController`'s. + */ + public final class PaneHostingController: NSHostingController, PreferencePane { + public let preferencePaneIdentifier: PaneIdentifier + public let preferencePaneTitle: String + public let toolbarItemIcon: NSImage - init( - identifier: PaneIdentifier, - title: String, - toolbarIcon: NSImage, - content: Content - ) { - preferencePaneIdentifier = identifier - preferencePaneTitle = title - toolbarItemIcon = toolbarIcon - super.init(rootView: content) - } + init( + identifier: PaneIdentifier, + title: String, + toolbarIcon: NSImage, + content: Content + ) { + preferencePaneIdentifier = identifier + preferencePaneTitle = title + toolbarItemIcon = toolbarIcon + super.init(rootView: content) + } - public convenience init(pane: Pane) { - self.init( - identifier: pane.identifier, - title: pane.title, - toolbarIcon: pane.toolbarIcon, - content: pane.content - ) - } + public convenience init(pane: Pane) { + self.init( + identifier: pane.identifier, + title: pane.title, + toolbarIcon: pane.toolbarIcon, + content: pane.content + ) + } - @available(*, unavailable) - @objc - dynamic required init?(coder _: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - } + @available(*, unavailable) + @objc + dynamic required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + } } @available(macOS 10.15, *) extension View { - /** - Applies font and color for a label used for describing a preference. - */ - public func preferenceDescription() -> some View { - font(.system(size: 11.0)) - // TODO: Use `.foregroundStyle` when targeting macOS 12. - .foregroundColor(.secondary) - } + /** + Applies font and color for a label used for describing a preference. + */ + public func preferenceDescription() -> some View { + font(.system(size: 11.0)) + // TODO: Use `.foregroundStyle` when targeting macOS 12. + .foregroundColor(.secondary) + } } diff --git a/Source/3rdParty/SindreSorhus/Preferences/PreferencePane.swift b/Source/3rdParty/SindreSorhus/Preferences/PreferencePane.swift index a62a0743..6729a9f4 100755 --- a/Source/3rdParty/SindreSorhus/Preferences/PreferencePane.swift +++ b/Source/3rdParty/SindreSorhus/Preferences/PreferencePane.swift @@ -21,39 +21,39 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import Cocoa extension Preferences { - public struct PaneIdentifier: Hashable, RawRepresentable, Codable { - public let rawValue: String + public struct PaneIdentifier: Hashable, RawRepresentable, Codable { + public let rawValue: String - public init(rawValue: String) { - self.rawValue = rawValue - } - } + public init(rawValue: String) { + self.rawValue = rawValue + } + } } public protocol PreferencePane: NSViewController { - var preferencePaneIdentifier: Preferences.PaneIdentifier { get } - var preferencePaneTitle: String { get } - var toolbarItemIcon: NSImage { get } + var preferencePaneIdentifier: Preferences.PaneIdentifier { get } + var preferencePaneTitle: String { get } + var toolbarItemIcon: NSImage { get } } extension PreferencePane { - public var toolbarItemIdentifier: NSToolbarItem.Identifier { - preferencePaneIdentifier.toolbarItemIdentifier - } + public var toolbarItemIdentifier: NSToolbarItem.Identifier { + preferencePaneIdentifier.toolbarItemIdentifier + } - public var toolbarItemIcon: NSImage { .empty } + public var toolbarItemIcon: NSImage { .empty } } extension Preferences.PaneIdentifier { - public init(_ rawValue: String) { - self.init(rawValue: rawValue) - } + public init(_ rawValue: String) { + self.init(rawValue: rawValue) + } - public init(fromToolbarItemIdentifier itemIdentifier: NSToolbarItem.Identifier) { - self.init(rawValue: itemIdentifier.rawValue) - } + public init(fromToolbarItemIdentifier itemIdentifier: NSToolbarItem.Identifier) { + self.init(rawValue: itemIdentifier.rawValue) + } - public var toolbarItemIdentifier: NSToolbarItem.Identifier { - NSToolbarItem.Identifier(rawValue) - } + public var toolbarItemIdentifier: NSToolbarItem.Identifier { + NSToolbarItem.Identifier(rawValue) + } } diff --git a/Source/3rdParty/SindreSorhus/Preferences/PreferencesStyle.swift b/Source/3rdParty/SindreSorhus/Preferences/PreferencesStyle.swift index c8c7130c..2f213969 100755 --- a/Source/3rdParty/SindreSorhus/Preferences/PreferencesStyle.swift +++ b/Source/3rdParty/SindreSorhus/Preferences/PreferencesStyle.swift @@ -21,8 +21,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import Cocoa extension Preferences { - public enum Style { - case toolbarItems - case segmentedControl - } + public enum Style { + case toolbarItems + case segmentedControl + } } diff --git a/Source/3rdParty/SindreSorhus/Preferences/PreferencesStyleController.swift b/Source/3rdParty/SindreSorhus/Preferences/PreferencesStyleController.swift index b696a53d..f631a2db 100755 --- a/Source/3rdParty/SindreSorhus/Preferences/PreferencesStyleController.swift +++ b/Source/3rdParty/SindreSorhus/Preferences/PreferencesStyleController.swift @@ -21,15 +21,15 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import Cocoa protocol PreferencesStyleController: AnyObject { - var delegate: PreferencesStyleControllerDelegate? { get set } - var isKeepingWindowCentered: Bool { get } + var delegate: PreferencesStyleControllerDelegate? { get set } + var isKeepingWindowCentered: Bool { get } - func toolbarItemIdentifiers() -> [NSToolbarItem.Identifier] - func toolbarItem(preferenceIdentifier: Preferences.PaneIdentifier) -> NSToolbarItem? - func selectTab(index: Int) + func toolbarItemIdentifiers() -> [NSToolbarItem.Identifier] + func toolbarItem(preferenceIdentifier: Preferences.PaneIdentifier) -> NSToolbarItem? + func selectTab(index: Int) } protocol PreferencesStyleControllerDelegate: AnyObject { - func activateTab(preferenceIdentifier: Preferences.PaneIdentifier, animated: Bool) - func activateTab(index: Int, animated: Bool) + func activateTab(preferenceIdentifier: Preferences.PaneIdentifier, animated: Bool) + func activateTab(index: Int, animated: Bool) } diff --git a/Source/3rdParty/SindreSorhus/Preferences/PreferencesTabViewController.swift b/Source/3rdParty/SindreSorhus/Preferences/PreferencesTabViewController.swift index 61cefb84..0070c3fe 100755 --- a/Source/3rdParty/SindreSorhus/Preferences/PreferencesTabViewController.swift +++ b/Source/3rdParty/SindreSorhus/Preferences/PreferencesTabViewController.swift @@ -21,242 +21,242 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import Cocoa final class PreferencesTabViewController: NSViewController, PreferencesStyleControllerDelegate { - private var activeTab: Int? - private var preferencePanes = [PreferencePane]() - private var style: Preferences.Style? - internal var preferencePanesCount: Int { preferencePanes.count } - private var preferencesStyleController: PreferencesStyleController! - private var isKeepingWindowCentered: Bool { preferencesStyleController.isKeepingWindowCentered } + private var activeTab: Int? + private var preferencePanes = [PreferencePane]() + private var style: Preferences.Style? + internal var preferencePanesCount: Int { preferencePanes.count } + private var preferencesStyleController: PreferencesStyleController! + private var isKeepingWindowCentered: Bool { preferencesStyleController.isKeepingWindowCentered } - private var toolbarItemIdentifiers: [NSToolbarItem.Identifier] { - preferencesStyleController?.toolbarItemIdentifiers() ?? [] - } + private var toolbarItemIdentifiers: [NSToolbarItem.Identifier] { + preferencesStyleController?.toolbarItemIdentifiers() ?? [] + } - var window: NSWindow! { view.window } + var window: NSWindow! { view.window } - var isAnimated = true + var isAnimated = true - var activeViewController: NSViewController? { - guard let activeTab = activeTab else { - return nil - } + var activeViewController: NSViewController? { + guard let activeTab = activeTab else { + return nil + } - return preferencePanes[activeTab] - } + return preferencePanes[activeTab] + } - override func loadView() { - view = NSView() - view.translatesAutoresizingMaskIntoConstraints = false - } + override func loadView() { + view = NSView() + view.translatesAutoresizingMaskIntoConstraints = false + } - func configure(preferencePanes: [PreferencePane], style: Preferences.Style) { - self.preferencePanes = preferencePanes - self.style = style - children = preferencePanes + func configure(preferencePanes: [PreferencePane], style: Preferences.Style) { + self.preferencePanes = preferencePanes + self.style = style + children = preferencePanes - let toolbar = NSToolbar(identifier: "PreferencesToolbar") - toolbar.allowsUserCustomization = false - toolbar.displayMode = .iconAndLabel - toolbar.showsBaselineSeparator = true - toolbar.delegate = self + let toolbar = NSToolbar(identifier: "PreferencesToolbar") + toolbar.allowsUserCustomization = false + toolbar.displayMode = .iconAndLabel + toolbar.showsBaselineSeparator = true + toolbar.delegate = self - switch style { - case .segmentedControl: - preferencesStyleController = SegmentedControlStyleViewController(preferencePanes: preferencePanes) - case .toolbarItems: - preferencesStyleController = ToolbarItemStyleViewController( - preferencePanes: preferencePanes, - toolbar: toolbar, - centerToolbarItems: false - ) - } - preferencesStyleController.delegate = self + switch style { + case .segmentedControl: + preferencesStyleController = SegmentedControlStyleViewController(preferencePanes: preferencePanes) + case .toolbarItems: + preferencesStyleController = ToolbarItemStyleViewController( + preferencePanes: preferencePanes, + toolbar: toolbar, + centerToolbarItems: false + ) + } + preferencesStyleController.delegate = self - // Called last so that `preferencesStyleController` can be asked for items. - window.toolbar = toolbar - } + // Called last so that `preferencesStyleController` can be asked for items. + window.toolbar = toolbar + } - func activateTab(preferencePane: PreferencePane, animated: Bool) { - activateTab(preferenceIdentifier: preferencePane.preferencePaneIdentifier, animated: animated) - } + func activateTab(preferencePane: PreferencePane, animated: Bool) { + activateTab(preferenceIdentifier: preferencePane.preferencePaneIdentifier, animated: animated) + } - func activateTab(preferenceIdentifier: Preferences.PaneIdentifier, animated: Bool) { - guard let index = (preferencePanes.firstIndex { $0.preferencePaneIdentifier == preferenceIdentifier }) else { - return activateTab(index: 0, animated: animated) - } + func activateTab(preferenceIdentifier: Preferences.PaneIdentifier, animated: Bool) { + guard let index = (preferencePanes.firstIndex { $0.preferencePaneIdentifier == preferenceIdentifier }) else { + return activateTab(index: 0, animated: animated) + } - activateTab(index: index, animated: animated) - } + activateTab(index: index, animated: animated) + } - func activateTab(index: Int, animated: Bool) { - defer { - activeTab = index - preferencesStyleController.selectTab(index: index) - updateWindowTitle(tabIndex: index) - } + func activateTab(index: Int, animated: Bool) { + defer { + activeTab = index + preferencesStyleController.selectTab(index: index) + updateWindowTitle(tabIndex: index) + } - if activeTab == nil { - immediatelyDisplayTab(index: index) - } else { - guard index != activeTab else { - return - } + if activeTab == nil { + immediatelyDisplayTab(index: index) + } else { + guard index != activeTab else { + return + } - animateTabTransition(index: index, animated: animated) - } - } + animateTabTransition(index: index, animated: animated) + } + } - func restoreInitialTab() { - if activeTab == nil { - activateTab(index: 0, animated: false) - } - } + func restoreInitialTab() { + if activeTab == nil { + activateTab(index: 0, animated: false) + } + } - private func updateWindowTitle(tabIndex _: Int) { - window.title = { - // if preferencePanes.count > 1 { - // return preferencePanes[tabIndex].preferencePaneTitle - // } else { - // let preferences = Localization[.preferences] - // let appName = Bundle.main.appName - // return "\(appName) \(preferences)" - // } - var preferencesTitleName = NSLocalizedString("vChewing Preferences…", comment: "") - preferencesTitleName.removeLast() - return preferencesTitleName - }() - } + private func updateWindowTitle(tabIndex _: Int) { + window.title = { + // if preferencePanes.count > 1 { + // return preferencePanes[tabIndex].preferencePaneTitle + // } else { + // let preferences = Localization[.preferences] + // let appName = Bundle.main.appName + // return "\(appName) \(preferences)" + // } + var preferencesTitleName = NSLocalizedString("vChewing Preferences…", comment: "") + preferencesTitleName.removeLast() + return preferencesTitleName + }() + } - /// Cached constraints that pin `childViewController` views to the content view. - private var activeChildViewConstraints = [NSLayoutConstraint]() + /// Cached constraints that pin `childViewController` views to the content view. + private var activeChildViewConstraints = [NSLayoutConstraint]() - private func immediatelyDisplayTab(index: Int) { - let toViewController = preferencePanes[index] - view.addSubview(toViewController.view) - activeChildViewConstraints = toViewController.view.constrainToSuperviewBounds() - setWindowFrame(for: toViewController, animated: false) - } + private func immediatelyDisplayTab(index: Int) { + let toViewController = preferencePanes[index] + view.addSubview(toViewController.view) + activeChildViewConstraints = toViewController.view.constrainToSuperviewBounds() + setWindowFrame(for: toViewController, animated: false) + } - private func animateTabTransition(index: Int, animated: Bool) { - guard let activeTab = activeTab else { - assertionFailure( - "animateTabTransition called before a tab was displayed; transition only works from one tab to another") - immediatelyDisplayTab(index: index) - return - } + private func animateTabTransition(index: Int, animated: Bool) { + guard let activeTab = activeTab else { + assertionFailure( + "animateTabTransition called before a tab was displayed; transition only works from one tab to another") + immediatelyDisplayTab(index: index) + return + } - let fromViewController = preferencePanes[activeTab] - let toViewController = preferencePanes[index] + let fromViewController = preferencePanes[activeTab] + let toViewController = preferencePanes[index] - // View controller animations only work on macOS 10.14 and newer. - let options: NSViewController.TransitionOptions - if #available(macOS 10.14, *) { - options = animated && isAnimated ? [.crossfade] : [] - } else { - options = [] - } + // View controller animations only work on macOS 10.14 and newer. + let options: NSViewController.TransitionOptions + if #available(macOS 10.14, *) { + options = animated && isAnimated ? [.crossfade] : [] + } else { + options = [] + } - view.removeConstraints(activeChildViewConstraints) + view.removeConstraints(activeChildViewConstraints) - transition( - from: fromViewController, - to: toViewController, - options: options - ) { [self] in - activeChildViewConstraints = toViewController.view.constrainToSuperviewBounds() - } - } + transition( + from: fromViewController, + to: toViewController, + options: options + ) { [self] in + activeChildViewConstraints = toViewController.view.constrainToSuperviewBounds() + } + } - override func transition( - from fromViewController: NSViewController, - to toViewController: NSViewController, - options: NSViewController.TransitionOptions = [], - completionHandler completion: (() -> Void)? = nil - ) { - let isAnimated = - options - .intersection([ - .crossfade, - .slideUp, - .slideDown, - .slideForward, - .slideBackward, - .slideLeft, - .slideRight, - ]) - .isEmpty == false + override func transition( + from fromViewController: NSViewController, + to toViewController: NSViewController, + options: NSViewController.TransitionOptions = [], + completionHandler completion: (() -> Void)? = nil + ) { + let isAnimated = + options + .intersection([ + .crossfade, + .slideUp, + .slideDown, + .slideForward, + .slideBackward, + .slideLeft, + .slideRight, + ]) + .isEmpty == false - if isAnimated { - NSAnimationContext.runAnimationGroup( - { context in - context.allowsImplicitAnimation = true - context.duration = 0.25 - context.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) - setWindowFrame(for: toViewController, animated: true) + if isAnimated { + NSAnimationContext.runAnimationGroup( + { context in + context.allowsImplicitAnimation = true + context.duration = 0.25 + context.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) + setWindowFrame(for: toViewController, animated: true) - super.transition( - from: fromViewController, - to: toViewController, - options: options, - completionHandler: completion - ) - }, completionHandler: nil - ) - } else { - super.transition( - from: fromViewController, - to: toViewController, - options: options, - completionHandler: completion - ) - } - } + super.transition( + from: fromViewController, + to: toViewController, + options: options, + completionHandler: completion + ) + }, completionHandler: nil + ) + } else { + super.transition( + from: fromViewController, + to: toViewController, + options: options, + completionHandler: completion + ) + } + } - private func setWindowFrame(for viewController: NSViewController, animated: Bool = false) { - guard let window = window else { - preconditionFailure() - } + private func setWindowFrame(for viewController: NSViewController, animated: Bool = false) { + guard let window = window else { + preconditionFailure() + } - let contentSize = viewController.view.fittingSize + let contentSize = viewController.view.fittingSize - let newWindowSize = window.frameRect(forContentRect: CGRect(origin: .zero, size: contentSize)).size - var frame = window.frame - frame.origin.y += frame.height - newWindowSize.height - frame.size = newWindowSize + let newWindowSize = window.frameRect(forContentRect: CGRect(origin: .zero, size: contentSize)).size + var frame = window.frame + frame.origin.y += frame.height - newWindowSize.height + frame.size = newWindowSize - if isKeepingWindowCentered { - let horizontalDiff = (window.frame.width - newWindowSize.width) / 2 - frame.origin.x += horizontalDiff - } + if isKeepingWindowCentered { + let horizontalDiff = (window.frame.width - newWindowSize.width) / 2 + frame.origin.x += horizontalDiff + } - let animatableWindow = animated ? window.animator() : window - animatableWindow.setFrame(frame, display: false) - } + let animatableWindow = animated ? window.animator() : window + animatableWindow.setFrame(frame, display: false) + } } extension PreferencesTabViewController: NSToolbarDelegate { - func toolbarDefaultItemIdentifiers(_: NSToolbar) -> [NSToolbarItem.Identifier] { - toolbarItemIdentifiers - } + func toolbarDefaultItemIdentifiers(_: NSToolbar) -> [NSToolbarItem.Identifier] { + toolbarItemIdentifiers + } - func toolbarAllowedItemIdentifiers(_: NSToolbar) -> [NSToolbarItem.Identifier] { - toolbarItemIdentifiers - } + func toolbarAllowedItemIdentifiers(_: NSToolbar) -> [NSToolbarItem.Identifier] { + toolbarItemIdentifiers + } - func toolbarSelectableItemIdentifiers(_: NSToolbar) -> [NSToolbarItem.Identifier] { - style == .segmentedControl ? [] : toolbarItemIdentifiers - } + func toolbarSelectableItemIdentifiers(_: NSToolbar) -> [NSToolbarItem.Identifier] { + style == .segmentedControl ? [] : toolbarItemIdentifiers + } - public func toolbar( - _: NSToolbar, - itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, - willBeInsertedIntoToolbar _: Bool - ) -> NSToolbarItem? { - if itemIdentifier == .flexibleSpace { - return nil - } + public func toolbar( + _: NSToolbar, + itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, + willBeInsertedIntoToolbar _: Bool + ) -> NSToolbarItem? { + if itemIdentifier == .flexibleSpace { + return nil + } - return preferencesStyleController.toolbarItem( - preferenceIdentifier: Preferences.PaneIdentifier(fromToolbarItemIdentifier: itemIdentifier)) - } + return preferencesStyleController.toolbarItem( + preferenceIdentifier: Preferences.PaneIdentifier(fromToolbarItemIdentifier: itemIdentifier)) + } } diff --git a/Source/3rdParty/SindreSorhus/Preferences/PreferencesWindowController.swift b/Source/3rdParty/SindreSorhus/Preferences/PreferencesWindowController.swift index 488a514f..0480271c 100755 --- a/Source/3rdParty/SindreSorhus/Preferences/PreferencesWindowController.swift +++ b/Source/3rdParty/SindreSorhus/Preferences/PreferencesWindowController.swift @@ -21,168 +21,168 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import Cocoa extension NSWindow.FrameAutosaveName { - static let preferences: NSWindow.FrameAutosaveName = "com.sindresorhus.Preferences.FrameAutosaveName" + static let preferences: NSWindow.FrameAutosaveName = "com.sindresorhus.Preferences.FrameAutosaveName" } public final class PreferencesWindowController: NSWindowController { - private let tabViewController = PreferencesTabViewController() + private let tabViewController = PreferencesTabViewController() - public var isAnimated: Bool { - get { tabViewController.isAnimated } - set { - tabViewController.isAnimated = newValue - } - } + public var isAnimated: Bool { + get { tabViewController.isAnimated } + set { + tabViewController.isAnimated = newValue + } + } - public var hidesToolbarForSingleItem: Bool { - didSet { - updateToolbarVisibility() - } - } + public var hidesToolbarForSingleItem: Bool { + didSet { + updateToolbarVisibility() + } + } - private func updateToolbarVisibility() { - window?.toolbar?.isVisible = - (hidesToolbarForSingleItem == false) - || (tabViewController.preferencePanesCount > 1) - } + private func updateToolbarVisibility() { + window?.toolbar?.isVisible = + (hidesToolbarForSingleItem == false) + || (tabViewController.preferencePanesCount > 1) + } - public init( - preferencePanes: [PreferencePane], - style: Preferences.Style = .toolbarItems, - animated: Bool = true, - hidesToolbarForSingleItem: Bool = true - ) { - precondition(!preferencePanes.isEmpty, "You need to set at least one view controller") + public init( + preferencePanes: [PreferencePane], + style: Preferences.Style = .toolbarItems, + animated: Bool = true, + hidesToolbarForSingleItem: Bool = true + ) { + precondition(!preferencePanes.isEmpty, "You need to set at least one view controller") - let window = UserInteractionPausableWindow( - contentRect: preferencePanes[0].view.bounds, - styleMask: [ - .titled, - .closable, - ], - backing: .buffered, - defer: true - ) - self.hidesToolbarForSingleItem = hidesToolbarForSingleItem - super.init(window: window) + let window = UserInteractionPausableWindow( + contentRect: preferencePanes[0].view.bounds, + styleMask: [ + .titled, + .closable, + ], + backing: .buffered, + defer: true + ) + self.hidesToolbarForSingleItem = hidesToolbarForSingleItem + super.init(window: window) - window.contentViewController = tabViewController + window.contentViewController = tabViewController - window.titleVisibility = { - switch style { - case .toolbarItems: - return .visible - case .segmentedControl: - return preferencePanes.count <= 1 ? .visible : .hidden - } - }() + window.titleVisibility = { + switch style { + case .toolbarItems: + return .visible + case .segmentedControl: + return preferencePanes.count <= 1 ? .visible : .hidden + } + }() - if #available(macOS 11.0, *), style == .toolbarItems { - window.toolbarStyle = .preference - } + if #available(macOS 11.0, *), style == .toolbarItems { + window.toolbarStyle = .preference + } - tabViewController.isAnimated = animated - tabViewController.configure(preferencePanes: preferencePanes, style: style) - updateToolbarVisibility() - } + tabViewController.isAnimated = animated + tabViewController.configure(preferencePanes: preferencePanes, style: style) + updateToolbarVisibility() + } - @available(*, unavailable) - override public init(window _: NSWindow?) { - fatalError("init(window:) is not supported, use init(preferences:style:animated:)") - } + @available(*, unavailable) + override public init(window _: NSWindow?) { + fatalError("init(window:) is not supported, use init(preferences:style:animated:)") + } - @available(*, unavailable) - public required init?(coder _: NSCoder) { - fatalError("init(coder:) is not supported, use init(preferences:style:animated:)") - } + @available(*, unavailable) + public required init?(coder _: NSCoder) { + fatalError("init(coder:) is not supported, use init(preferences:style:animated:)") + } - /** - Show the preferences window and brings it to front. + /** + Show the preferences window and brings it to front. - If you pass a `Preferences.PaneIdentifier`, the window will activate the corresponding tab. + If you pass a `Preferences.PaneIdentifier`, the window will activate the corresponding tab. - - Parameter preferencePane: Identifier of the preference pane to display, or `nil` to show the tab that was open when the user last closed the window. + - Parameter preferencePane: Identifier of the preference pane to display, or `nil` to show the tab that was open when the user last closed the window. - - Note: Unless you need to open a specific pane, prefer not to pass a parameter at all or `nil`. + - Note: Unless you need to open a specific pane, prefer not to pass a parameter at all or `nil`. - - See `close()` to close the window again. - - See `showWindow(_:)` to show the window without the convenience of activating the app. - */ - public func show(preferencePane preferenceIdentifier: Preferences.PaneIdentifier? = nil) { - if let preferenceIdentifier = preferenceIdentifier { - tabViewController.activateTab(preferenceIdentifier: preferenceIdentifier, animated: false) - } else { - tabViewController.restoreInitialTab() - } + - See `close()` to close the window again. + - See `showWindow(_:)` to show the window without the convenience of activating the app. + */ + public func show(preferencePane preferenceIdentifier: Preferences.PaneIdentifier? = nil) { + if let preferenceIdentifier = preferenceIdentifier { + tabViewController.activateTab(preferenceIdentifier: preferenceIdentifier, animated: false) + } else { + tabViewController.restoreInitialTab() + } - showWindow(self) - restoreWindowPosition() - NSApp.activate(ignoringOtherApps: true) - } + showWindow(self) + restoreWindowPosition() + NSApp.activate(ignoringOtherApps: true) + } - private func restoreWindowPosition() { - guard - let window = window, - let screenContainingWindow = window.screen - else { - return - } + private func restoreWindowPosition() { + guard + let window = window, + let screenContainingWindow = window.screen + else { + return + } - window.setFrameOrigin( - CGPoint( - x: screenContainingWindow.visibleFrame.midX - window.frame.width / 2, - y: screenContainingWindow.visibleFrame.midY - window.frame.height / 2 - )) - window.setFrameUsingName(.preferences) - window.setFrameAutosaveName(.preferences) - } + window.setFrameOrigin( + CGPoint( + x: screenContainingWindow.visibleFrame.midX - window.frame.width / 2, + y: screenContainingWindow.visibleFrame.midY - window.frame.height / 2 + )) + window.setFrameUsingName(.preferences) + window.setFrameAutosaveName(.preferences) + } } extension PreferencesWindowController { - /// Returns the active pane if it responds to the given action. - public override func supplementalTarget(forAction action: Selector, sender: Any?) -> Any? { - if let target = super.supplementalTarget(forAction: action, sender: sender) { - return target - } + /// Returns the active pane if it responds to the given action. + public override func supplementalTarget(forAction action: Selector, sender: Any?) -> Any? { + if let target = super.supplementalTarget(forAction: action, sender: sender) { + return target + } - guard let activeViewController = tabViewController.activeViewController else { - return nil - } + guard let activeViewController = tabViewController.activeViewController else { + return nil + } - if let target = NSApp.target(forAction: action, to: activeViewController, from: sender) as? NSResponder, - target.responds(to: action) - { - return target - } + if let target = NSApp.target(forAction: action, to: activeViewController, from: sender) as? NSResponder, + target.responds(to: action) + { + return target + } - if let target = activeViewController.supplementalTarget(forAction: action, sender: sender) as? NSResponder, - target.responds(to: action) - { - return target - } + if let target = activeViewController.supplementalTarget(forAction: action, sender: sender) as? NSResponder, + target.responds(to: action) + { + return target + } - return nil - } + return nil + } } @available(macOS 10.15, *) extension PreferencesWindowController { - /** - Create a preferences window from only SwiftUI-based preference panes. - */ - public convenience init( - panes: [PreferencePaneConvertible], - style: Preferences.Style = .toolbarItems, - animated: Bool = true, - hidesToolbarForSingleItem: Bool = true - ) { - let preferencePanes = panes.map { $0.asPreferencePane() } + /** + Create a preferences window from only SwiftUI-based preference panes. + */ + public convenience init( + panes: [PreferencePaneConvertible], + style: Preferences.Style = .toolbarItems, + animated: Bool = true, + hidesToolbarForSingleItem: Bool = true + ) { + let preferencePanes = panes.map { $0.asPreferencePane() } - self.init( - preferencePanes: preferencePanes, - style: style, - animated: animated, - hidesToolbarForSingleItem: hidesToolbarForSingleItem - ) - } + self.init( + preferencePanes: preferencePanes, + style: style, + animated: animated, + hidesToolbarForSingleItem: hidesToolbarForSingleItem + ) + } } diff --git a/Source/3rdParty/SindreSorhus/Preferences/Section.swift b/Source/3rdParty/SindreSorhus/Preferences/Section.swift index 6ca000c8..86c7a637 100755 --- a/Source/3rdParty/SindreSorhus/Preferences/Section.swift +++ b/Source/3rdParty/SindreSorhus/Preferences/Section.swift @@ -22,119 +22,119 @@ import SwiftUI @available(macOS 10.15, *) extension Preferences { - /** - Represents a section with right-aligned title and optional bottom divider. - */ - @available(macOS 10.15, *) - public struct Section: View { - /** - Preference key holding max width of section labels. - */ - private struct LabelWidthPreferenceKey: PreferenceKey { - typealias Value = Double + /** + Represents a section with right-aligned title and optional bottom divider. + */ + @available(macOS 10.15, *) + public struct Section: View { + /** + Preference key holding max width of section labels. + */ + private struct LabelWidthPreferenceKey: PreferenceKey { + typealias Value = Double - static var defaultValue = 0.0 + static var defaultValue = 0.0 - static func reduce(value: inout Double, nextValue: () -> Double) { - let next = nextValue() - value = next > value ? next : value - } - } + static func reduce(value: inout Double, nextValue: () -> Double) { + let next = nextValue() + value = next > value ? next : value + } + } - /** - Convenience overlay for finding a label's dimensions using `GeometryReader`. - */ - private struct LabelOverlay: View { - var body: some View { - GeometryReader { geometry in - Color.clear - .preference(key: LabelWidthPreferenceKey.self, value: Double(geometry.size.width)) - } - } - } + /** + Convenience overlay for finding a label's dimensions using `GeometryReader`. + */ + private struct LabelOverlay: View { + var body: some View { + GeometryReader { geometry in + Color.clear + .preference(key: LabelWidthPreferenceKey.self, value: Double(geometry.size.width)) + } + } + } - /** - Convenience modifier for applying `LabelWidthPreferenceKey`. - */ - struct LabelWidthModifier: ViewModifier { - @Binding var maximumWidth: Double + /** + Convenience modifier for applying `LabelWidthPreferenceKey`. + */ + struct LabelWidthModifier: ViewModifier { + @Binding var maximumWidth: Double - func body(content: Content) -> some View { - content - .onPreferenceChange(LabelWidthPreferenceKey.self) { newMaximumWidth in - maximumWidth = Double(newMaximumWidth) - } - } - } + func body(content: Content) -> some View { + content + .onPreferenceChange(LabelWidthPreferenceKey.self) { newMaximumWidth in + maximumWidth = Double(newMaximumWidth) + } + } + } - public let label: AnyView - public let content: AnyView - public let bottomDivider: Bool - public let verticalAlignment: VerticalAlignment + public let label: AnyView + public let content: AnyView + public let bottomDivider: Bool + public let verticalAlignment: VerticalAlignment - /** - A section is responsible for controlling a single preference. + /** + A section is responsible for controlling a single preference. - - Parameters: - - bottomDivider: Whether to place a `Divider` after the section content. Default is `false`. - - verticalAlignement: The vertical alignment of the section content. - - verticalAlignment: - - label: A view describing preference handled by this section. - - content: A content view. - */ - public init( - bottomDivider: Bool = false, - verticalAlignment: VerticalAlignment = .firstTextBaseline, - label: @escaping () -> Label, - @ViewBuilder content: @escaping () -> Content - ) { - self.label = label() - .overlay(LabelOverlay()) - .eraseToAnyView() // TODO: Remove use of `AnyView`. - self.bottomDivider = bottomDivider - self.verticalAlignment = verticalAlignment - let stack = VStack(alignment: .leading) { content() } - self.content = stack.eraseToAnyView() - } + - Parameters: + - bottomDivider: Whether to place a `Divider` after the section content. Default is `false`. + - verticalAlignement: The vertical alignment of the section content. + - verticalAlignment: + - label: A view describing preference handled by this section. + - content: A content view. + */ + public init( + bottomDivider: Bool = false, + verticalAlignment: VerticalAlignment = .firstTextBaseline, + label: @escaping () -> Label, + @ViewBuilder content: @escaping () -> Content + ) { + self.label = label() + .overlay(LabelOverlay()) + .eraseToAnyView() // TODO: Remove use of `AnyView`. + self.bottomDivider = bottomDivider + self.verticalAlignment = verticalAlignment + let stack = VStack(alignment: .leading) { content() } + self.content = stack.eraseToAnyView() + } - /** - Creates instance of section, responsible for controling single preference with `Text` as a `Label`. + /** + Creates instance of section, responsible for controling single preference with `Text` as a `Label`. - - Parameters: - - title: A string describing preference handled by this section. - - bottomDivider: Whether to place a `Divider` after the section content. Default is `false`. - - verticalAlignement: The vertical alignment of the section content. - - verticalAlignment: - - content: A content view. - */ - public init( - title: String, - bottomDivider: Bool = false, - verticalAlignment: VerticalAlignment = .firstTextBaseline, - @ViewBuilder content: @escaping () -> Content - ) { - let textLabel = { - Text(title) - .font(.system(size: 13.0)) - .overlay(LabelOverlay()) - .eraseToAnyView() - } + - Parameters: + - title: A string describing preference handled by this section. + - bottomDivider: Whether to place a `Divider` after the section content. Default is `false`. + - verticalAlignement: The vertical alignment of the section content. + - verticalAlignment: + - content: A content view. + */ + public init( + title: String, + bottomDivider: Bool = false, + verticalAlignment: VerticalAlignment = .firstTextBaseline, + @ViewBuilder content: @escaping () -> Content + ) { + let textLabel = { + Text(title) + .font(.system(size: 13.0)) + .overlay(LabelOverlay()) + .eraseToAnyView() + } - self.init( - bottomDivider: bottomDivider, - verticalAlignment: verticalAlignment, - label: textLabel, - content: content - ) - } + self.init( + bottomDivider: bottomDivider, + verticalAlignment: verticalAlignment, + label: textLabel, + content: content + ) + } - public var body: some View { - HStack(alignment: verticalAlignment) { - label - .alignmentGuide(.preferenceSectionLabel) { $0[.trailing] } - content - Spacer() - } - } - } + public var body: some View { + HStack(alignment: verticalAlignment) { + label + .alignmentGuide(.preferenceSectionLabel) { $0[.trailing] } + content + Spacer() + } + } + } } diff --git a/Source/3rdParty/SindreSorhus/Preferences/SegmentedControlStyleViewController.swift b/Source/3rdParty/SindreSorhus/Preferences/SegmentedControlStyleViewController.swift index c1aba49a..4d2f8579 100755 --- a/Source/3rdParty/SindreSorhus/Preferences/SegmentedControlStyleViewController.swift +++ b/Source/3rdParty/SindreSorhus/Preferences/SegmentedControlStyleViewController.swift @@ -21,139 +21,139 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import Cocoa extension NSToolbarItem.Identifier { - static let toolbarSegmentedControlItem = Self("toolbarSegmentedControlItem") + static let toolbarSegmentedControlItem = Self("toolbarSegmentedControlItem") } extension NSUserInterfaceItemIdentifier { - static let toolbarSegmentedControl = Self("toolbarSegmentedControl") + static let toolbarSegmentedControl = Self("toolbarSegmentedControl") } final class SegmentedControlStyleViewController: NSViewController, PreferencesStyleController { - var segmentedControl: NSSegmentedControl! { - get { view as? NSSegmentedControl } - set { - view = newValue - } - } + var segmentedControl: NSSegmentedControl! { + get { view as? NSSegmentedControl } + set { + view = newValue + } + } - var isKeepingWindowCentered: Bool { true } + var isKeepingWindowCentered: Bool { true } - weak var delegate: PreferencesStyleControllerDelegate? + weak var delegate: PreferencesStyleControllerDelegate? - private var preferencePanes: [PreferencePane]! + private var preferencePanes: [PreferencePane]! - required init(preferencePanes: [PreferencePane]) { - super.init(nibName: nil, bundle: nil) - self.preferencePanes = preferencePanes - } + required init(preferencePanes: [PreferencePane]) { + super.init(nibName: nil, bundle: nil) + self.preferencePanes = preferencePanes + } - @available(*, unavailable) - required init?(coder _: NSCoder) { - fatalError("init(coder:) has not been implemented") - } + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } - override func loadView() { - view = createSegmentedControl(preferencePanes: preferencePanes) - } + override func loadView() { + view = createSegmentedControl(preferencePanes: preferencePanes) + } - fileprivate func createSegmentedControl(preferencePanes: [PreferencePane]) -> NSSegmentedControl { - let segmentedControl = NSSegmentedControl() - segmentedControl.segmentCount = preferencePanes.count - segmentedControl.segmentStyle = .texturedSquare - segmentedControl.target = self - segmentedControl.action = #selector(segmentedControlAction) - segmentedControl.identifier = .toolbarSegmentedControl + fileprivate func createSegmentedControl(preferencePanes: [PreferencePane]) -> NSSegmentedControl { + let segmentedControl = NSSegmentedControl() + segmentedControl.segmentCount = preferencePanes.count + segmentedControl.segmentStyle = .texturedSquare + segmentedControl.target = self + segmentedControl.action = #selector(segmentedControlAction) + segmentedControl.identifier = .toolbarSegmentedControl - if let cell = segmentedControl.cell as? NSSegmentedCell { - cell.controlSize = .regular - cell.trackingMode = .selectOne - } + if let cell = segmentedControl.cell as? NSSegmentedCell { + cell.controlSize = .regular + cell.trackingMode = .selectOne + } - let segmentSize: CGSize = { - let insets = CGSize(width: 36, height: 12) - var maxSize = CGSize.zero + let segmentSize: CGSize = { + let insets = CGSize(width: 36, height: 12) + var maxSize = CGSize.zero - for preferencePane in preferencePanes { - let title = preferencePane.preferencePaneTitle - let titleSize = title.size( - withAttributes: [ - .font: NSFont.systemFont(ofSize: NSFont.systemFontSize(for: .regular)) - ] - ) + for preferencePane in preferencePanes { + let title = preferencePane.preferencePaneTitle + let titleSize = title.size( + withAttributes: [ + .font: NSFont.systemFont(ofSize: NSFont.systemFontSize(for: .regular)) + ] + ) - maxSize = CGSize( - width: max(titleSize.width, maxSize.width), - height: max(titleSize.height, maxSize.height) - ) - } + maxSize = CGSize( + width: max(titleSize.width, maxSize.width), + height: max(titleSize.height, maxSize.height) + ) + } - return CGSize( - width: maxSize.width + insets.width, - height: maxSize.height + insets.height - ) - }() + return CGSize( + width: maxSize.width + insets.width, + height: maxSize.height + insets.height + ) + }() - let segmentBorderWidth = CGFloat(preferencePanes.count) + 1 - let segmentWidth = segmentSize.width * CGFloat(preferencePanes.count) + segmentBorderWidth - let segmentHeight = segmentSize.height - segmentedControl.frame = CGRect(x: 0, y: 0, width: segmentWidth, height: segmentHeight) + let segmentBorderWidth = CGFloat(preferencePanes.count) + 1 + let segmentWidth = segmentSize.width * CGFloat(preferencePanes.count) + segmentBorderWidth + let segmentHeight = segmentSize.height + segmentedControl.frame = CGRect(x: 0, y: 0, width: segmentWidth, height: segmentHeight) - for (index, preferencePane) in preferencePanes.enumerated() { - segmentedControl.setLabel(preferencePane.preferencePaneTitle, forSegment: index) - segmentedControl.setWidth(segmentSize.width, forSegment: index) - if let cell = segmentedControl.cell as? NSSegmentedCell { - cell.setTag(index, forSegment: index) - } - } + for (index, preferencePane) in preferencePanes.enumerated() { + segmentedControl.setLabel(preferencePane.preferencePaneTitle, forSegment: index) + segmentedControl.setWidth(segmentSize.width, forSegment: index) + if let cell = segmentedControl.cell as? NSSegmentedCell { + cell.setTag(index, forSegment: index) + } + } - return segmentedControl - } + return segmentedControl + } - @IBAction private func segmentedControlAction(_ control: NSSegmentedControl) { - delegate?.activateTab(index: control.selectedSegment, animated: true) - } + @IBAction private func segmentedControlAction(_ control: NSSegmentedControl) { + delegate?.activateTab(index: control.selectedSegment, animated: true) + } - func selectTab(index: Int) { - segmentedControl.selectedSegment = index - } + func selectTab(index: Int) { + segmentedControl.selectedSegment = index + } - func toolbarItemIdentifiers() -> [NSToolbarItem.Identifier] { - [ - .flexibleSpace, - .toolbarSegmentedControlItem, - .flexibleSpace, - ] - } + func toolbarItemIdentifiers() -> [NSToolbarItem.Identifier] { + [ + .flexibleSpace, + .toolbarSegmentedControlItem, + .flexibleSpace, + ] + } - func toolbarItem(preferenceIdentifier: Preferences.PaneIdentifier) -> NSToolbarItem? { - let toolbarItemIdentifier = preferenceIdentifier.toolbarItemIdentifier - precondition(toolbarItemIdentifier == .toolbarSegmentedControlItem) + func toolbarItem(preferenceIdentifier: Preferences.PaneIdentifier) -> NSToolbarItem? { + let toolbarItemIdentifier = preferenceIdentifier.toolbarItemIdentifier + precondition(toolbarItemIdentifier == .toolbarSegmentedControlItem) - // When the segments outgrow the window, we need to provide a group of - // NSToolbarItems with custom menu item labels and action handling for the - // context menu that pops up at the right edge of the window. - let toolbarItemGroup = NSToolbarItemGroup(itemIdentifier: toolbarItemIdentifier) - toolbarItemGroup.view = segmentedControl - toolbarItemGroup.subitems = preferencePanes.enumerated().map { index, preferenceable -> NSToolbarItem in - let item = NSToolbarItem(itemIdentifier: .init("segment-\(preferenceable.preferencePaneTitle)")) - item.label = preferenceable.preferencePaneTitle + // When the segments outgrow the window, we need to provide a group of + // NSToolbarItems with custom menu item labels and action handling for the + // context menu that pops up at the right edge of the window. + let toolbarItemGroup = NSToolbarItemGroup(itemIdentifier: toolbarItemIdentifier) + toolbarItemGroup.view = segmentedControl + toolbarItemGroup.subitems = preferencePanes.enumerated().map { index, preferenceable -> NSToolbarItem in + let item = NSToolbarItem(itemIdentifier: .init("segment-\(preferenceable.preferencePaneTitle)")) + item.label = preferenceable.preferencePaneTitle - let menuItem = NSMenuItem( - title: preferenceable.preferencePaneTitle, - action: #selector(segmentedControlMenuAction), - keyEquivalent: "" - ) - menuItem.tag = index - menuItem.target = self - item.menuFormRepresentation = menuItem + let menuItem = NSMenuItem( + title: preferenceable.preferencePaneTitle, + action: #selector(segmentedControlMenuAction), + keyEquivalent: "" + ) + menuItem.tag = index + menuItem.target = self + item.menuFormRepresentation = menuItem - return item - } + return item + } - return toolbarItemGroup - } + return toolbarItemGroup + } - @IBAction private func segmentedControlMenuAction(_ menuItem: NSMenuItem) { - delegate?.activateTab(index: menuItem.tag, animated: true) - } + @IBAction private func segmentedControlMenuAction(_ menuItem: NSMenuItem) { + delegate?.activateTab(index: menuItem.tag, animated: true) + } } diff --git a/Source/3rdParty/SindreSorhus/Preferences/ToolbarItemStyleViewController.swift b/Source/3rdParty/SindreSorhus/Preferences/ToolbarItemStyleViewController.swift index 34cdf5c1..6b64cfce 100755 --- a/Source/3rdParty/SindreSorhus/Preferences/ToolbarItemStyleViewController.swift +++ b/Source/3rdParty/SindreSorhus/Preferences/ToolbarItemStyleViewController.swift @@ -21,57 +21,57 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import Cocoa final class ToolbarItemStyleViewController: NSObject, PreferencesStyleController { - let toolbar: NSToolbar - let centerToolbarItems: Bool - let preferencePanes: [PreferencePane] - var isKeepingWindowCentered: Bool { centerToolbarItems } - weak var delegate: PreferencesStyleControllerDelegate? + let toolbar: NSToolbar + let centerToolbarItems: Bool + let preferencePanes: [PreferencePane] + var isKeepingWindowCentered: Bool { centerToolbarItems } + weak var delegate: PreferencesStyleControllerDelegate? - init(preferencePanes: [PreferencePane], toolbar: NSToolbar, centerToolbarItems: Bool) { - self.preferencePanes = preferencePanes - self.toolbar = toolbar - self.centerToolbarItems = centerToolbarItems - } + init(preferencePanes: [PreferencePane], toolbar: NSToolbar, centerToolbarItems: Bool) { + self.preferencePanes = preferencePanes + self.toolbar = toolbar + self.centerToolbarItems = centerToolbarItems + } - func toolbarItemIdentifiers() -> [NSToolbarItem.Identifier] { - var toolbarItemIdentifiers = [NSToolbarItem.Identifier]() + func toolbarItemIdentifiers() -> [NSToolbarItem.Identifier] { + var toolbarItemIdentifiers = [NSToolbarItem.Identifier]() - if centerToolbarItems { - toolbarItemIdentifiers.append(.flexibleSpace) - } + if centerToolbarItems { + toolbarItemIdentifiers.append(.flexibleSpace) + } - for preferencePane in preferencePanes { - toolbarItemIdentifiers.append(preferencePane.toolbarItemIdentifier) - } + for preferencePane in preferencePanes { + toolbarItemIdentifiers.append(preferencePane.toolbarItemIdentifier) + } - if centerToolbarItems { - toolbarItemIdentifiers.append(.flexibleSpace) - } + if centerToolbarItems { + toolbarItemIdentifiers.append(.flexibleSpace) + } - return toolbarItemIdentifiers - } + return toolbarItemIdentifiers + } - func toolbarItem(preferenceIdentifier: Preferences.PaneIdentifier) -> NSToolbarItem? { - guard let preference = (preferencePanes.first { $0.preferencePaneIdentifier == preferenceIdentifier }) else { - preconditionFailure() - } + func toolbarItem(preferenceIdentifier: Preferences.PaneIdentifier) -> NSToolbarItem? { + guard let preference = (preferencePanes.first { $0.preferencePaneIdentifier == preferenceIdentifier }) else { + preconditionFailure() + } - let toolbarItem = NSToolbarItem(itemIdentifier: preferenceIdentifier.toolbarItemIdentifier) - toolbarItem.label = preference.preferencePaneTitle - toolbarItem.image = preference.toolbarItemIcon - toolbarItem.target = self - toolbarItem.action = #selector(toolbarItemSelected) - return toolbarItem - } + let toolbarItem = NSToolbarItem(itemIdentifier: preferenceIdentifier.toolbarItemIdentifier) + toolbarItem.label = preference.preferencePaneTitle + toolbarItem.image = preference.toolbarItemIcon + toolbarItem.target = self + toolbarItem.action = #selector(toolbarItemSelected) + return toolbarItem + } - @IBAction private func toolbarItemSelected(_ toolbarItem: NSToolbarItem) { - delegate?.activateTab( - preferenceIdentifier: Preferences.PaneIdentifier(fromToolbarItemIdentifier: toolbarItem.itemIdentifier), - animated: true - ) - } + @IBAction private func toolbarItemSelected(_ toolbarItem: NSToolbarItem) { + delegate?.activateTab( + preferenceIdentifier: Preferences.PaneIdentifier(fromToolbarItemIdentifier: toolbarItem.itemIdentifier), + animated: true + ) + } - func selectTab(index: Int) { - toolbar.selectedItemIdentifier = preferencePanes[index].toolbarItemIdentifier - } + func selectTab(index: Int) { + toolbar.selectedItemIdentifier = preferencePanes[index].toolbarItemIdentifier + } } diff --git a/Source/3rdParty/SindreSorhus/Preferences/Utilities.swift b/Source/3rdParty/SindreSorhus/Preferences/Utilities.swift index eaa3b415..f2c27a15 100755 --- a/Source/3rdParty/SindreSorhus/Preferences/Utilities.swift +++ b/Source/3rdParty/SindreSorhus/Preferences/Utilities.swift @@ -22,117 +22,117 @@ import Cocoa import SwiftUI extension NSImage { - static var empty: NSImage { NSImage(size: .zero) } + static var empty: NSImage { NSImage(size: .zero) } } extension NSView { - @discardableResult - func constrainToSuperviewBounds() -> [NSLayoutConstraint] { - guard let superview = superview else { - preconditionFailure("superview has to be set first") - } + @discardableResult + func constrainToSuperviewBounds() -> [NSLayoutConstraint] { + guard let superview = superview else { + preconditionFailure("superview has to be set first") + } - var result = [NSLayoutConstraint]() - result.append( - contentsOf: NSLayoutConstraint.constraints( - withVisualFormat: "H:|-0-[subview]-0-|", options: .directionLeadingToTrailing, metrics: nil, - views: ["subview": self] - )) - result.append( - contentsOf: NSLayoutConstraint.constraints( - withVisualFormat: "V:|-0-[subview]-0-|", options: .directionLeadingToTrailing, metrics: nil, - views: ["subview": self] - )) - translatesAutoresizingMaskIntoConstraints = false - superview.addConstraints(result) + var result = [NSLayoutConstraint]() + result.append( + contentsOf: NSLayoutConstraint.constraints( + withVisualFormat: "H:|-0-[subview]-0-|", options: .directionLeadingToTrailing, metrics: nil, + views: ["subview": self] + )) + result.append( + contentsOf: NSLayoutConstraint.constraints( + withVisualFormat: "V:|-0-[subview]-0-|", options: .directionLeadingToTrailing, metrics: nil, + views: ["subview": self] + )) + translatesAutoresizingMaskIntoConstraints = false + superview.addConstraints(result) - return result - } + return result + } } extension NSEvent { - /// Events triggered by user interaction. - static let userInteractionEvents: [NSEvent.EventType] = { - var events: [NSEvent.EventType] = [ - .leftMouseDown, - .leftMouseUp, - .rightMouseDown, - .rightMouseUp, - .leftMouseDragged, - .rightMouseDragged, - .keyDown, - .keyUp, - .scrollWheel, - .tabletPoint, - .otherMouseDown, - .otherMouseUp, - .otherMouseDragged, - .gesture, - .magnify, - .swipe, - .rotate, - .beginGesture, - .endGesture, - .smartMagnify, - .quickLook, - .directTouch, - ] + /// Events triggered by user interaction. + static let userInteractionEvents: [NSEvent.EventType] = { + var events: [NSEvent.EventType] = [ + .leftMouseDown, + .leftMouseUp, + .rightMouseDown, + .rightMouseUp, + .leftMouseDragged, + .rightMouseDragged, + .keyDown, + .keyUp, + .scrollWheel, + .tabletPoint, + .otherMouseDown, + .otherMouseUp, + .otherMouseDragged, + .gesture, + .magnify, + .swipe, + .rotate, + .beginGesture, + .endGesture, + .smartMagnify, + .quickLook, + .directTouch, + ] - if #available(macOS 10.10.3, *) { - events.append(.pressure) - } + if #available(macOS 10.10.3, *) { + events.append(.pressure) + } - return events - }() + return events + }() - /// Whether the event was triggered by user interaction. - var isUserInteraction: Bool { NSEvent.userInteractionEvents.contains(type) } + /// Whether the event was triggered by user interaction. + var isUserInteraction: Bool { NSEvent.userInteractionEvents.contains(type) } } extension Bundle { - var appName: String { - string(forInfoDictionaryKey: "CFBundleDisplayName") - ?? string(forInfoDictionaryKey: "CFBundleName") - ?? string(forInfoDictionaryKey: "CFBundleExecutable") - ?? "" - } + var appName: String { + string(forInfoDictionaryKey: "CFBundleDisplayName") + ?? string(forInfoDictionaryKey: "CFBundleName") + ?? string(forInfoDictionaryKey: "CFBundleExecutable") + ?? "" + } - private func string(forInfoDictionaryKey key: String) -> String? { - // `object(forInfoDictionaryKey:)` prefers localized info dictionary over the regular one automatically - object(forInfoDictionaryKey: key) as? String - } + private func string(forInfoDictionaryKey key: String) -> String? { + // `object(forInfoDictionaryKey:)` prefers localized info dictionary over the regular one automatically + object(forInfoDictionaryKey: key) as? String + } } /// A window that allows you to disable all user interactions via `isUserInteractionEnabled`. /// /// Used to avoid breaking animations when the user clicks too fast. Disable user interactions during animations and you're set. class UserInteractionPausableWindow: NSWindow { - var isUserInteractionEnabled = true + var isUserInteractionEnabled = true - override func sendEvent(_ event: NSEvent) { - guard isUserInteractionEnabled || !event.isUserInteraction else { - return - } + override func sendEvent(_ event: NSEvent) { + guard isUserInteractionEnabled || !event.isUserInteraction else { + return + } - super.sendEvent(event) - } + super.sendEvent(event) + } - override func responds(to selector: Selector!) -> Bool { - // Deactivate toolbar interactions from the Main Menu. - if selector == #selector(NSWindow.toggleToolbarShown(_:)) { - return false - } + override func responds(to selector: Selector!) -> Bool { + // Deactivate toolbar interactions from the Main Menu. + if selector == #selector(NSWindow.toggleToolbarShown(_:)) { + return false + } - return super.responds(to: selector) - } + return super.responds(to: selector) + } } @available(macOS 10.15, *) extension View { - /** - Equivalent to `.eraseToAnyPublisher()` from the Combine framework. - */ - func eraseToAnyView() -> AnyView { - AnyView(self) - } + /** + Equivalent to `.eraseToAnyPublisher()` from the Combine framework. + */ + func eraseToAnyView() -> AnyView { + AnyView(self) + } } diff --git a/Source/3rdParty/VDKComboBox/VDKComboBox.swift b/Source/3rdParty/VDKComboBox/VDKComboBox.swift index ddc8f055..6a4690a7 100644 --- a/Source/3rdParty/VDKComboBox/VDKComboBox.swift +++ b/Source/3rdParty/VDKComboBox/VDKComboBox.swift @@ -10,62 +10,62 @@ import SwiftUI // Ref: https://stackoverflow.com/a/71058587/4162914 @available(macOS 11.0, *) struct ComboBox: NSViewRepresentable { - // The items that will show up in the pop-up menu: - var items: [String] + // The items that will show up in the pop-up menu: + var items: [String] - // The property on our parent view that gets synced to the current - // stringValue of the NSComboBox, whether the user typed it in or - // selected it from the list: - @Binding var text: String + // The property on our parent view that gets synced to the current + // stringValue of the NSComboBox, whether the user typed it in or + // selected it from the list: + @Binding var text: String - func makeCoordinator() -> Coordinator { - Coordinator(self) - } + func makeCoordinator() -> Coordinator { + Coordinator(self) + } - func makeNSView(context: Context) -> NSComboBox { - let comboBox = NSComboBox() - comboBox.usesDataSource = false - comboBox.completes = false - comboBox.delegate = context.coordinator - comboBox.intercellSpacing = NSSize(width: 0.0, height: 10.0) - return comboBox - } + func makeNSView(context: Context) -> NSComboBox { + let comboBox = NSComboBox() + comboBox.usesDataSource = false + comboBox.completes = false + comboBox.delegate = context.coordinator + comboBox.intercellSpacing = NSSize(width: 0.0, height: 10.0) + return comboBox + } - func updateNSView(_ nsView: NSComboBox, context: Context) { - nsView.removeAllItems() - nsView.addItems(withObjectValues: items) + func updateNSView(_ nsView: NSComboBox, context: Context) { + nsView.removeAllItems() + nsView.addItems(withObjectValues: items) - // ComboBox doesn't automatically select the item matching its text; - // we must do that manually. But we need the delegate to ignore that - // selection-change or we'll get a "state modified during view update; - // will cause undefined behavior" warning. - context.coordinator.ignoreSelectionChanges = true - nsView.stringValue = text - nsView.selectItem(withObjectValue: text) - context.coordinator.ignoreSelectionChanges = false - } + // ComboBox doesn't automatically select the item matching its text; + // we must do that manually. But we need the delegate to ignore that + // selection-change or we'll get a "state modified during view update; + // will cause undefined behavior" warning. + context.coordinator.ignoreSelectionChanges = true + nsView.stringValue = text + nsView.selectItem(withObjectValue: text) + context.coordinator.ignoreSelectionChanges = false + } - class Coordinator: NSObject, NSComboBoxDelegate { - var parent: ComboBox - var ignoreSelectionChanges: Bool = false + class Coordinator: NSObject, NSComboBoxDelegate { + var parent: ComboBox + var ignoreSelectionChanges: Bool = false - init(_ parent: ComboBox) { - self.parent = parent - } + init(_ parent: ComboBox) { + self.parent = parent + } - func comboBoxSelectionDidChange(_ notification: Notification) { - if !ignoreSelectionChanges, - let box: NSComboBox = notification.object as? NSComboBox, - let newStringValue: String = box.objectValueOfSelectedItem as? String - { - parent.text = newStringValue - } - } + func comboBoxSelectionDidChange(_ notification: Notification) { + if !ignoreSelectionChanges, + let box: NSComboBox = notification.object as? NSComboBox, + let newStringValue: String = box.objectValueOfSelectedItem as? String + { + parent.text = newStringValue + } + } - func controlTextDidEndEditing(_ obj: Notification) { - if let textField = obj.object as? NSTextField { - parent.text = textField.stringValue - } - } - } + func controlTextDidEndEditing(_ obj: Notification) { + if let textField = obj.object as? NSTextField { + parent.text = textField.stringValue + } + } + } } diff --git a/Source/Headers/vChewing-Bridging-Header.h b/Source/Headers/vChewing-Bridging-Header.h index 3f79ac94..fd5078af 100644 --- a/Source/Headers/vChewing-Bridging-Header.h +++ b/Source/Headers/vChewing-Bridging-Header.h @@ -30,5 +30,5 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. @import Foundation; -#import "KeyHandler.h" -#import "mgrLangModel.h" +#import "CTools.h" +#import "Composer.hh" diff --git a/Source/Modules/AppDelegate.swift b/Source/Modules/AppDelegate.swift index a99dc6f4..d243e9d1 100644 --- a/Source/Modules/AppDelegate.swift +++ b/Source/Modules/AppDelegate.swift @@ -29,216 +29,214 @@ import InputMethodKit @objc(AppDelegate) class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelegate, - FSEventStreamHelperDelegate + FSEventStreamHelperDelegate { - func helper(_: FSEventStreamHelper, didReceive _: [FSEventStreamHelper.Event]) { - // 拖 100ms 再重載,畢竟有些有特殊需求的使用者可能會想使用巨型自訂語彙檔案。 - DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) { - if mgrPrefs.shouldAutoReloadUserDataFiles { - IME.initLangModels(userOnly: true) - } - } - } + func helper(_: FSEventStreamHelper, didReceive _: [FSEventStreamHelper.Event]) { + // 拖 100ms 再重載,畢竟有些有特殊需求的使用者可能會想使用巨型自訂語彙檔案。 + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) { + if mgrPrefs.shouldAutoReloadUserDataFiles { + IME.initLangModels(userOnly: true) + } + } + } - // let vChewingKeyLayoutBundle = Bundle.init(path: URL(fileURLWithPath: Bundle.main.resourcePath ?? "").appendingPathComponent("vChewingKeyLayout.bundle").path) + // let vChewingKeyLayoutBundle = Bundle.init(path: URL(fileURLWithPath: Bundle.main.resourcePath ?? "").appendingPathComponent("vChewingKeyLayout.bundle").path) - @IBOutlet var window: NSWindow? - private var ctlPrefWindowInstance: ctlPrefWindow? - private var ctlAboutWindowInstance: ctlAboutWindow? // New About Window - private var checkTask: URLSessionTask? - private var updateNextStepURL: URL? - private var fsStreamHelper = FSEventStreamHelper( - path: mgrLangModel.dataFolderPath(isDefaultFolder: false), - queue: DispatchQueue(label: "vChewing User Phrases") - ) - private var currentAlertType: String = "" + @IBOutlet var window: NSWindow? + private var ctlPrefWindowInstance: ctlPrefWindow? + private var ctlAboutWindowInstance: ctlAboutWindow? // New About Window + private var checkTask: URLSessionTask? + private var updateNextStepURL: URL? + private var fsStreamHelper = FSEventStreamHelper( + path: mgrLangModel.dataFolderPath(isDefaultFolder: false), + queue: DispatchQueue(label: "vChewing User Phrases") + ) + private var currentAlertType: String = "" - // 補上 dealloc - deinit { - ctlPrefWindowInstance = nil - ctlAboutWindowInstance = nil - checkTask = nil - updateNextStepURL = nil - fsStreamHelper.stop() - fsStreamHelper.delegate = nil - } + // 補上 dealloc + deinit { + ctlPrefWindowInstance = nil + ctlAboutWindowInstance = nil + checkTask = nil + updateNextStepURL = nil + fsStreamHelper.stop() + fsStreamHelper.delegate = nil + } - func applicationDidFinishLaunching(_: Notification) { - IME.initLangModels(userOnly: false) - fsStreamHelper.delegate = self - _ = fsStreamHelper.start() + func applicationDidFinishLaunching(_: Notification) { + IME.initLangModels(userOnly: false) + fsStreamHelper.delegate = self + _ = fsStreamHelper.start() - mgrPrefs.setMissingDefaults() + mgrPrefs.setMissingDefaults() - // 只要使用者沒有勾選檢查更新、沒有主動做出要檢查更新的操作,就不要檢查更新。 - if (UserDefaults.standard.object(forKey: VersionUpdateApi.kCheckUpdateAutomatically) != nil) == true { - checkForUpdate() - } - } + // 只要使用者沒有勾選檢查更新、沒有主動做出要檢查更新的操作,就不要檢查更新。 + if (UserDefaults.standard.object(forKey: VersionUpdateApi.kCheckUpdateAutomatically) != nil) == true { + checkForUpdate() + } + } - @objc func showPreferences() { - if ctlPrefWindowInstance == nil { - ctlPrefWindowInstance = ctlPrefWindow.init(windowNibName: "frmPrefWindow") - } - ctlPrefWindowInstance?.window?.center() - ctlPrefWindowInstance?.window?.orderFrontRegardless() // 逼著屬性視窗往最前方顯示 - ctlPrefWindowInstance?.window?.level = .statusBar - ctlPrefWindowInstance?.window?.titlebarAppearsTransparent = true - NSApp.setActivationPolicy(.accessory) - } + func showPreferences() { + if ctlPrefWindowInstance == nil { + ctlPrefWindowInstance = ctlPrefWindow.init(windowNibName: "frmPrefWindow") + } + ctlPrefWindowInstance?.window?.center() + ctlPrefWindowInstance?.window?.orderFrontRegardless() // 逼著屬性視窗往最前方顯示 + ctlPrefWindowInstance?.window?.level = .statusBar + ctlPrefWindowInstance?.window?.titlebarAppearsTransparent = true + NSApp.setActivationPolicy(.accessory) + } - // New About Window - @objc func showAbout() { - if ctlAboutWindowInstance == nil { - ctlAboutWindowInstance = ctlAboutWindow.init(windowNibName: "frmAboutWindow") - } - ctlAboutWindowInstance?.window?.center() - ctlAboutWindowInstance?.window?.orderFrontRegardless() // 逼著關於視窗往最前方顯示 - ctlAboutWindowInstance?.window?.level = .statusBar - NSApp.setActivationPolicy(.accessory) - } + // New About Window + func showAbout() { + if ctlAboutWindowInstance == nil { + ctlAboutWindowInstance = ctlAboutWindow.init(windowNibName: "frmAboutWindow") + } + ctlAboutWindowInstance?.window?.center() + ctlAboutWindowInstance?.window?.orderFrontRegardless() // 逼著關於視窗往最前方顯示 + ctlAboutWindowInstance?.window?.level = .statusBar + NSApp.setActivationPolicy(.accessory) + } - @objc(checkForUpdate) - func checkForUpdate() { - checkForUpdate(forced: false) - } + func checkForUpdate() { + checkForUpdate(forced: false) + } - @objc(checkForUpdateForced:) - func checkForUpdate(forced: Bool) { - if checkTask != nil { - // busy - return - } + func checkForUpdate(forced: Bool) { + if checkTask != nil { + // busy + return + } - // time for update? - if !forced { - if UserDefaults.standard.bool(forKey: VersionUpdateApi.kCheckUpdateAutomatically) == false { - return - } - let now = Date() - let date = UserDefaults.standard.object(forKey: VersionUpdateApi.kNextUpdateCheckDateKey) as? Date ?? now - if now.compare(date) == .orderedAscending { - return - } - } + // time for update? + if !forced { + if UserDefaults.standard.bool(forKey: VersionUpdateApi.kCheckUpdateAutomatically) == false { + return + } + let now = Date() + let date = UserDefaults.standard.object(forKey: VersionUpdateApi.kNextUpdateCheckDateKey) as? Date ?? now + if now.compare(date) == .orderedAscending { + return + } + } - let nextUpdateDate = Date(timeInterval: VersionUpdateApi.kNextCheckInterval, since: Date()) - UserDefaults.standard.set(nextUpdateDate, forKey: VersionUpdateApi.kNextUpdateCheckDateKey) + let nextUpdateDate = Date(timeInterval: VersionUpdateApi.kNextCheckInterval, since: Date()) + UserDefaults.standard.set(nextUpdateDate, forKey: VersionUpdateApi.kNextUpdateCheckDateKey) - checkTask = VersionUpdateApi.check(forced: forced) { [self] result in - defer { - checkTask = nil - } - switch result { - case .success(let apiResult): - switch apiResult { - case .shouldUpdate(let report): - updateNextStepURL = report.siteUrl - let content = String( - format: NSLocalizedString( - "You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?%@", - comment: "" - ), - report.currentShortVersion, - report.currentVersion, - report.remoteShortVersion, - report.remoteVersion, - report.versionDescription - ) - IME.prtDebugIntel("vChewingDebug: \(content)") - currentAlertType = "Update" - ctlNonModalAlertWindow.shared.show( - title: NSLocalizedString( - "New Version Available", comment: "" - ), - content: content, - confirmButtonTitle: NSLocalizedString( - "Visit Website", comment: "" - ), - cancelButtonTitle: NSLocalizedString( - "Not Now", comment: "" - ), - cancelAsDefault: false, - delegate: self - ) - NSApp.setActivationPolicy(.accessory) - case .noNeedToUpdate, .ignored: - break - } - case .failure(let error): - switch error { - case VersionUpdateApiError.connectionError(let message): - let title = NSLocalizedString( - "Update Check Failed", comment: "" - ) - let content = String( - format: NSLocalizedString( - "There may be no internet connection or the server failed to respond.\n\nError message: %@", - comment: "" - ), message - ) - let buttonTitle = NSLocalizedString("Dismiss", comment: "") - IME.prtDebugIntel("vChewingDebug: \(content)") - currentAlertType = "Update" - ctlNonModalAlertWindow.shared.show( - title: title, content: content, - confirmButtonTitle: buttonTitle, - cancelButtonTitle: nil, - cancelAsDefault: false, delegate: nil - ) - NSApp.setActivationPolicy(.accessory) - default: - break - } - } - } - } + checkTask = VersionUpdateApi.check(forced: forced) { [self] result in + defer { + checkTask = nil + } + switch result { + case .success(let apiResult): + switch apiResult { + case .shouldUpdate(let report): + updateNextStepURL = report.siteUrl + let content = String( + format: NSLocalizedString( + "You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?%@", + comment: "" + ), + report.currentShortVersion, + report.currentVersion, + report.remoteShortVersion, + report.remoteVersion, + report.versionDescription + ) + IME.prtDebugIntel("vChewingDebug: \(content)") + currentAlertType = "Update" + ctlNonModalAlertWindow.shared.show( + title: NSLocalizedString( + "New Version Available", comment: "" + ), + content: content, + confirmButtonTitle: NSLocalizedString( + "Visit Website", comment: "" + ), + cancelButtonTitle: NSLocalizedString( + "Not Now", comment: "" + ), + cancelAsDefault: false, + delegate: self + ) + NSApp.setActivationPolicy(.accessory) + case .noNeedToUpdate, .ignored: + break + } + case .failure(let error): + switch error { + case VersionUpdateApiError.connectionError(let message): + let title = NSLocalizedString( + "Update Check Failed", comment: "" + ) + let content = String( + format: NSLocalizedString( + "There may be no internet connection or the server failed to respond.\n\nError message: %@", + comment: "" + ), message + ) + let buttonTitle = NSLocalizedString("Dismiss", comment: "") + IME.prtDebugIntel("vChewingDebug: \(content)") + currentAlertType = "Update" + ctlNonModalAlertWindow.shared.show( + title: title, content: content, + confirmButtonTitle: buttonTitle, + cancelButtonTitle: nil, + cancelAsDefault: false, delegate: nil + ) + NSApp.setActivationPolicy(.accessory) + default: + break + } + } + } + } - func selfUninstall() { - currentAlertType = "Uninstall" - let content = String( - format: NSLocalizedString( - "This will remove vChewing Input Method from this user account, requiring your confirmation.", - comment: "" - )) - ctlNonModalAlertWindow.shared.show( - title: NSLocalizedString("Uninstallation", comment: ""), content: content, - confirmButtonTitle: NSLocalizedString("OK", comment: ""), - cancelButtonTitle: NSLocalizedString("Not Now", comment: ""), cancelAsDefault: false, - delegate: self - ) - NSApp.setActivationPolicy(.accessory) - } + func selfUninstall() { + currentAlertType = "Uninstall" + let content = String( + format: NSLocalizedString( + "This will remove vChewing Input Method from this user account, requiring your confirmation.", + comment: "" + )) + ctlNonModalAlertWindow.shared.show( + title: NSLocalizedString("Uninstallation", comment: ""), content: content, + confirmButtonTitle: NSLocalizedString("OK", comment: ""), + cancelButtonTitle: NSLocalizedString("Not Now", comment: ""), cancelAsDefault: false, + delegate: self + ) + NSApp.setActivationPolicy(.accessory) + } - func ctlNonModalAlertWindowDidConfirm(_: ctlNonModalAlertWindow) { - switch currentAlertType { - case "Uninstall": - NSWorkspace.shared.openFile( - mgrLangModel.dataFolderPath(isDefaultFolder: true), withApplication: "Finder" - ) - IME.uninstall(isSudo: false, selfKill: true) - case "Update": - if let updateNextStepURL = updateNextStepURL { - NSWorkspace.shared.open(updateNextStepURL) - } - updateNextStepURL = nil - default: - break - } - } + func ctlNonModalAlertWindowDidConfirm(_: ctlNonModalAlertWindow) { + switch currentAlertType { + case "Uninstall": + NSWorkspace.shared.openFile( + mgrLangModel.dataFolderPath(isDefaultFolder: true), withApplication: "Finder" + ) + IME.uninstall(isSudo: false, selfKill: true) + case "Update": + if let updateNextStepURL = updateNextStepURL { + NSWorkspace.shared.open(updateNextStepURL) + } + updateNextStepURL = nil + default: + break + } + } - func ctlNonModalAlertWindowDidCancel(_: ctlNonModalAlertWindow) { - switch currentAlertType { - case "Update": - updateNextStepURL = nil - default: - break - } - } + func ctlNonModalAlertWindowDidCancel(_: ctlNonModalAlertWindow) { + switch currentAlertType { + case "Update": + updateNextStepURL = nil + default: + break + } + } - // New About Window - @IBAction func about(_: Any) { - (NSApp.delegate as? AppDelegate)?.showAbout() - NSApplication.shared.activate(ignoringOtherApps: true) - } + // New About Window + @IBAction func about(_: Any) { + (NSApp.delegate as? AppDelegate)?.showAbout() + NSApplication.shared.activate(ignoringOtherApps: true) + } } diff --git a/Source/Modules/ControllerModules/AppleKeyboardConverter.swift b/Source/Modules/ControllerModules/AppleKeyboardConverter.swift index 41db1d45..07e444db 100644 --- a/Source/Modules/ControllerModules/AppleKeyboardConverter.swift +++ b/Source/Modules/ControllerModules/AppleKeyboardConverter.swift @@ -25,303 +25,303 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import Cocoa class AppleKeyboardConverter: NSObject { - static let arrDynamicBasicKeyLayout: [String] = [ - "com.apple.keylayout.ZhuyinBopomofo", - "com.apple.keylayout.ZhuyinEten", - "org.atelierInmu.vChewing.keyLayouts.vchewingdachen", - "org.atelierInmu.vChewing.keyLayouts.vchewingmitac", - "org.atelierInmu.vChewing.keyLayouts.vchewingibm", - "org.atelierInmu.vChewing.keyLayouts.vchewingseigyou", - "org.atelierInmu.vChewing.keyLayouts.vchewingeten", - "org.unknown.keylayout.vChewingDachen", - "org.unknown.keylayout.vChewingFakeSeigyou", - "org.unknown.keylayout.vChewingETen", - "org.unknown.keylayout.vChewingIBM", - "org.unknown.keylayout.vChewingMiTAC", - ] - @objc class func isDynamicBasicKeyboardLayoutEnabled() -> Bool { - AppleKeyboardConverter.arrDynamicBasicKeyLayout.contains(mgrPrefs.basicKeyboardLayout) - } + static let arrDynamicBasicKeyLayout: [String] = [ + "com.apple.keylayout.ZhuyinBopomofo", + "com.apple.keylayout.ZhuyinEten", + "org.atelierInmu.vChewing.keyLayouts.vchewingdachen", + "org.atelierInmu.vChewing.keyLayouts.vchewingmitac", + "org.atelierInmu.vChewing.keyLayouts.vchewingibm", + "org.atelierInmu.vChewing.keyLayouts.vchewingseigyou", + "org.atelierInmu.vChewing.keyLayouts.vchewingeten", + "org.unknown.keylayout.vChewingDachen", + "org.unknown.keylayout.vChewingFakeSeigyou", + "org.unknown.keylayout.vChewingETen", + "org.unknown.keylayout.vChewingIBM", + "org.unknown.keylayout.vChewingMiTAC", + ] + class func isDynamicBasicKeyboardLayoutEnabled() -> Bool { + AppleKeyboardConverter.arrDynamicBasicKeyLayout.contains(mgrPrefs.basicKeyboardLayout) + } - // 處理 Apple 注音鍵盤佈局類型。 - @objc class func cnvApple2ABC(_ charCode: UniChar) -> UniChar { - var charCode = charCode - // 在按鍵資訊被送往 OVMandarin 之前,先轉換為可以被 OVMandarin 正常處理的資訊。 - if isDynamicBasicKeyboardLayoutEnabled() { - // 針對不同的 Apple 動態鍵盤佈局糾正大寫英文輸入。 - switch mgrPrefs.basicKeyboardLayout { - case "com.apple.keylayout.ZhuyinBopomofo": - do { - if charCode == 97 { charCode = UniChar(65) } - if charCode == 98 { charCode = UniChar(66) } - if charCode == 99 { charCode = UniChar(67) } - if charCode == 100 { charCode = UniChar(68) } - if charCode == 101 { charCode = UniChar(69) } - if charCode == 102 { charCode = UniChar(70) } - if charCode == 103 { charCode = UniChar(71) } - if charCode == 104 { charCode = UniChar(72) } - if charCode == 105 { charCode = UniChar(73) } - if charCode == 106 { charCode = UniChar(74) } - if charCode == 107 { charCode = UniChar(75) } - if charCode == 108 { charCode = UniChar(76) } - if charCode == 109 { charCode = UniChar(77) } - if charCode == 110 { charCode = UniChar(78) } - if charCode == 111 { charCode = UniChar(79) } - if charCode == 112 { charCode = UniChar(80) } - if charCode == 113 { charCode = UniChar(81) } - if charCode == 114 { charCode = UniChar(82) } - if charCode == 115 { charCode = UniChar(83) } - if charCode == 116 { charCode = UniChar(84) } - if charCode == 117 { charCode = UniChar(85) } - if charCode == 118 { charCode = UniChar(86) } - if charCode == 119 { charCode = UniChar(87) } - if charCode == 120 { charCode = UniChar(88) } - if charCode == 121 { charCode = UniChar(89) } - if charCode == 122 { charCode = UniChar(90) } - } - case "com.apple.keylayout.ZhuyinEten": - do { - if charCode == 65345 { charCode = UniChar(65) } - if charCode == 65346 { charCode = UniChar(66) } - if charCode == 65347 { charCode = UniChar(67) } - if charCode == 65348 { charCode = UniChar(68) } - if charCode == 65349 { charCode = UniChar(69) } - if charCode == 65350 { charCode = UniChar(70) } - if charCode == 65351 { charCode = UniChar(71) } - if charCode == 65352 { charCode = UniChar(72) } - if charCode == 65353 { charCode = UniChar(73) } - if charCode == 65354 { charCode = UniChar(74) } - if charCode == 65355 { charCode = UniChar(75) } - if charCode == 65356 { charCode = UniChar(76) } - if charCode == 65357 { charCode = UniChar(77) } - if charCode == 65358 { charCode = UniChar(78) } - if charCode == 65359 { charCode = UniChar(79) } - if charCode == 65360 { charCode = UniChar(80) } - if charCode == 65361 { charCode = UniChar(81) } - if charCode == 65362 { charCode = UniChar(82) } - if charCode == 65363 { charCode = UniChar(83) } - if charCode == 65364 { charCode = UniChar(84) } - if charCode == 65365 { charCode = UniChar(85) } - if charCode == 65366 { charCode = UniChar(86) } - if charCode == 65367 { charCode = UniChar(87) } - if charCode == 65368 { charCode = UniChar(88) } - if charCode == 65369 { charCode = UniChar(89) } - if charCode == 65370 { charCode = UniChar(90) } - } - default: break - } - // 注音鍵群。 - if charCode == 12573 { charCode = UniChar(44) } - if charCode == 12582 { charCode = UniChar(45) } - if charCode == 12577 { charCode = UniChar(46) } - if charCode == 12581 { charCode = UniChar(47) } - if charCode == 12578 { charCode = UniChar(48) } - if charCode == 12549 { charCode = UniChar(49) } - if charCode == 12553 { charCode = UniChar(50) } - if charCode == 711 { charCode = UniChar(51) } - if charCode == 715 { charCode = UniChar(52) } - if charCode == 12563 { charCode = UniChar(53) } - if charCode == 714 { charCode = UniChar(54) } - if charCode == 729 { charCode = UniChar(55) } - if charCode == 12570 { charCode = UniChar(56) } - if charCode == 12574 { charCode = UniChar(57) } - if charCode == 12580 { charCode = UniChar(59) } - if charCode == 12551 { charCode = UniChar(97) } - if charCode == 12566 { charCode = UniChar(98) } - if charCode == 12559 { charCode = UniChar(99) } - if charCode == 12558 { charCode = UniChar(100) } - if charCode == 12557 { charCode = UniChar(101) } - if charCode == 12561 { charCode = UniChar(102) } - if charCode == 12565 { charCode = UniChar(103) } - if charCode == 12568 { charCode = UniChar(104) } - if charCode == 12571 { charCode = UniChar(105) } - if charCode == 12584 { charCode = UniChar(106) } - if charCode == 12572 { charCode = UniChar(107) } - if charCode == 12576 { charCode = UniChar(108) } - if charCode == 12585 { charCode = UniChar(109) } - if charCode == 12569 { charCode = UniChar(110) } - if charCode == 12575 { charCode = UniChar(111) } - if charCode == 12579 { charCode = UniChar(112) } - if charCode == 12550 { charCode = UniChar(113) } - if charCode == 12560 { charCode = UniChar(114) } - if charCode == 12555 { charCode = UniChar(115) } - if charCode == 12564 { charCode = UniChar(116) } - if charCode == 12583 { charCode = UniChar(117) } - if charCode == 12562 { charCode = UniChar(118) } - if charCode == 12554 { charCode = UniChar(119) } - if charCode == 12556 { charCode = UniChar(120) } - if charCode == 12567 { charCode = UniChar(121) } - if charCode == 12552 { charCode = UniChar(122) } - // 除了數字鍵區以外的標點符號。 - if charCode == 12289 { charCode = UniChar(92) } - if charCode == 12300 { charCode = UniChar(91) } - if charCode == 12301 { charCode = UniChar(93) } - if charCode == 12302 { charCode = UniChar(123) } - if charCode == 12303 { charCode = UniChar(125) } - if charCode == 65292 { charCode = UniChar(60) } - if charCode == 12290 { charCode = UniChar(62) } - // 摁了 SHIFT 之後的數字區的符號。 - if charCode == 65281 { charCode = UniChar(33) } - if charCode == 65312 { charCode = UniChar(64) } - if charCode == 65283 { charCode = UniChar(35) } - if charCode == 65284 { charCode = UniChar(36) } - if charCode == 65285 { charCode = UniChar(37) } - if charCode == 65087 { charCode = UniChar(94) } - if charCode == 65286 { charCode = UniChar(38) } - if charCode == 65290 { charCode = UniChar(42) } - if charCode == 65288 { charCode = UniChar(40) } - if charCode == 65289 { charCode = UniChar(41) } - // 摁了 Alt 的符號。 - if charCode == 8212 { charCode = UniChar(45) } - // Apple 倚天注音佈局追加符號糾正項目。 - if mgrPrefs.basicKeyboardLayout == "com.apple.keylayout.ZhuyinEten" { - if charCode == 65343 { charCode = UniChar(95) } - if charCode == 65306 { charCode = UniChar(58) } - if charCode == 65311 { charCode = UniChar(63) } - if charCode == 65291 { charCode = UniChar(43) } - if charCode == 65372 { charCode = UniChar(124) } - } - } - return charCode - } + // 處理 Apple 注音鍵盤佈局類型。 + class func cnvApple2ABC(_ charCode: UniChar) -> UniChar { + var charCode = charCode + // 在按鍵資訊被送往 OVMandarin 之前,先轉換為可以被 OVMandarin 正常處理的資訊。 + if isDynamicBasicKeyboardLayoutEnabled() { + // 針對不同的 Apple 動態鍵盤佈局糾正大寫英文輸入。 + switch mgrPrefs.basicKeyboardLayout { + case "com.apple.keylayout.ZhuyinBopomofo": + do { + if charCode == 97 { charCode = UniChar(65) } + if charCode == 98 { charCode = UniChar(66) } + if charCode == 99 { charCode = UniChar(67) } + if charCode == 100 { charCode = UniChar(68) } + if charCode == 101 { charCode = UniChar(69) } + if charCode == 102 { charCode = UniChar(70) } + if charCode == 103 { charCode = UniChar(71) } + if charCode == 104 { charCode = UniChar(72) } + if charCode == 105 { charCode = UniChar(73) } + if charCode == 106 { charCode = UniChar(74) } + if charCode == 107 { charCode = UniChar(75) } + if charCode == 108 { charCode = UniChar(76) } + if charCode == 109 { charCode = UniChar(77) } + if charCode == 110 { charCode = UniChar(78) } + if charCode == 111 { charCode = UniChar(79) } + if charCode == 112 { charCode = UniChar(80) } + if charCode == 113 { charCode = UniChar(81) } + if charCode == 114 { charCode = UniChar(82) } + if charCode == 115 { charCode = UniChar(83) } + if charCode == 116 { charCode = UniChar(84) } + if charCode == 117 { charCode = UniChar(85) } + if charCode == 118 { charCode = UniChar(86) } + if charCode == 119 { charCode = UniChar(87) } + if charCode == 120 { charCode = UniChar(88) } + if charCode == 121 { charCode = UniChar(89) } + if charCode == 122 { charCode = UniChar(90) } + } + case "com.apple.keylayout.ZhuyinEten": + do { + if charCode == 65345 { charCode = UniChar(65) } + if charCode == 65346 { charCode = UniChar(66) } + if charCode == 65347 { charCode = UniChar(67) } + if charCode == 65348 { charCode = UniChar(68) } + if charCode == 65349 { charCode = UniChar(69) } + if charCode == 65350 { charCode = UniChar(70) } + if charCode == 65351 { charCode = UniChar(71) } + if charCode == 65352 { charCode = UniChar(72) } + if charCode == 65353 { charCode = UniChar(73) } + if charCode == 65354 { charCode = UniChar(74) } + if charCode == 65355 { charCode = UniChar(75) } + if charCode == 65356 { charCode = UniChar(76) } + if charCode == 65357 { charCode = UniChar(77) } + if charCode == 65358 { charCode = UniChar(78) } + if charCode == 65359 { charCode = UniChar(79) } + if charCode == 65360 { charCode = UniChar(80) } + if charCode == 65361 { charCode = UniChar(81) } + if charCode == 65362 { charCode = UniChar(82) } + if charCode == 65363 { charCode = UniChar(83) } + if charCode == 65364 { charCode = UniChar(84) } + if charCode == 65365 { charCode = UniChar(85) } + if charCode == 65366 { charCode = UniChar(86) } + if charCode == 65367 { charCode = UniChar(87) } + if charCode == 65368 { charCode = UniChar(88) } + if charCode == 65369 { charCode = UniChar(89) } + if charCode == 65370 { charCode = UniChar(90) } + } + default: break + } + // 注音鍵群。 + if charCode == 12573 { charCode = UniChar(44) } + if charCode == 12582 { charCode = UniChar(45) } + if charCode == 12577 { charCode = UniChar(46) } + if charCode == 12581 { charCode = UniChar(47) } + if charCode == 12578 { charCode = UniChar(48) } + if charCode == 12549 { charCode = UniChar(49) } + if charCode == 12553 { charCode = UniChar(50) } + if charCode == 711 { charCode = UniChar(51) } + if charCode == 715 { charCode = UniChar(52) } + if charCode == 12563 { charCode = UniChar(53) } + if charCode == 714 { charCode = UniChar(54) } + if charCode == 729 { charCode = UniChar(55) } + if charCode == 12570 { charCode = UniChar(56) } + if charCode == 12574 { charCode = UniChar(57) } + if charCode == 12580 { charCode = UniChar(59) } + if charCode == 12551 { charCode = UniChar(97) } + if charCode == 12566 { charCode = UniChar(98) } + if charCode == 12559 { charCode = UniChar(99) } + if charCode == 12558 { charCode = UniChar(100) } + if charCode == 12557 { charCode = UniChar(101) } + if charCode == 12561 { charCode = UniChar(102) } + if charCode == 12565 { charCode = UniChar(103) } + if charCode == 12568 { charCode = UniChar(104) } + if charCode == 12571 { charCode = UniChar(105) } + if charCode == 12584 { charCode = UniChar(106) } + if charCode == 12572 { charCode = UniChar(107) } + if charCode == 12576 { charCode = UniChar(108) } + if charCode == 12585 { charCode = UniChar(109) } + if charCode == 12569 { charCode = UniChar(110) } + if charCode == 12575 { charCode = UniChar(111) } + if charCode == 12579 { charCode = UniChar(112) } + if charCode == 12550 { charCode = UniChar(113) } + if charCode == 12560 { charCode = UniChar(114) } + if charCode == 12555 { charCode = UniChar(115) } + if charCode == 12564 { charCode = UniChar(116) } + if charCode == 12583 { charCode = UniChar(117) } + if charCode == 12562 { charCode = UniChar(118) } + if charCode == 12554 { charCode = UniChar(119) } + if charCode == 12556 { charCode = UniChar(120) } + if charCode == 12567 { charCode = UniChar(121) } + if charCode == 12552 { charCode = UniChar(122) } + // 除了數字鍵區以外的標點符號。 + if charCode == 12289 { charCode = UniChar(92) } + if charCode == 12300 { charCode = UniChar(91) } + if charCode == 12301 { charCode = UniChar(93) } + if charCode == 12302 { charCode = UniChar(123) } + if charCode == 12303 { charCode = UniChar(125) } + if charCode == 65292 { charCode = UniChar(60) } + if charCode == 12290 { charCode = UniChar(62) } + // 摁了 SHIFT 之後的數字區的符號。 + if charCode == 65281 { charCode = UniChar(33) } + if charCode == 65312 { charCode = UniChar(64) } + if charCode == 65283 { charCode = UniChar(35) } + if charCode == 65284 { charCode = UniChar(36) } + if charCode == 65285 { charCode = UniChar(37) } + if charCode == 65087 { charCode = UniChar(94) } + if charCode == 65286 { charCode = UniChar(38) } + if charCode == 65290 { charCode = UniChar(42) } + if charCode == 65288 { charCode = UniChar(40) } + if charCode == 65289 { charCode = UniChar(41) } + // 摁了 Alt 的符號。 + if charCode == 8212 { charCode = UniChar(45) } + // Apple 倚天注音佈局追加符號糾正項目。 + if mgrPrefs.basicKeyboardLayout == "com.apple.keylayout.ZhuyinEten" { + if charCode == 65343 { charCode = UniChar(95) } + if charCode == 65306 { charCode = UniChar(58) } + if charCode == 65311 { charCode = UniChar(63) } + if charCode == 65291 { charCode = UniChar(43) } + if charCode == 65372 { charCode = UniChar(124) } + } + } + return charCode + } - @objc class func cnvStringApple2ABC(_ strProcessed: String) -> String { - var strProcessed = strProcessed - if isDynamicBasicKeyboardLayoutEnabled() { - // 針對不同的 Apple 動態鍵盤佈局糾正大寫英文輸入。 - switch mgrPrefs.basicKeyboardLayout { - case "com.apple.keylayout.ZhuyinBopomofo": - do { - if strProcessed == "a" { strProcessed = "A" } - if strProcessed == "b" { strProcessed = "B" } - if strProcessed == "c" { strProcessed = "C" } - if strProcessed == "d" { strProcessed = "D" } - if strProcessed == "e" { strProcessed = "E" } - if strProcessed == "f" { strProcessed = "F" } - if strProcessed == "g" { strProcessed = "G" } - if strProcessed == "h" { strProcessed = "H" } - if strProcessed == "i" { strProcessed = "I" } - if strProcessed == "j" { strProcessed = "J" } - if strProcessed == "k" { strProcessed = "K" } - if strProcessed == "l" { strProcessed = "L" } - if strProcessed == "m" { strProcessed = "M" } - if strProcessed == "n" { strProcessed = "N" } - if strProcessed == "o" { strProcessed = "O" } - if strProcessed == "p" { strProcessed = "P" } - if strProcessed == "q" { strProcessed = "Q" } - if strProcessed == "r" { strProcessed = "R" } - if strProcessed == "s" { strProcessed = "S" } - if strProcessed == "t" { strProcessed = "T" } - if strProcessed == "u" { strProcessed = "U" } - if strProcessed == "v" { strProcessed = "V" } - if strProcessed == "w" { strProcessed = "W" } - if strProcessed == "x" { strProcessed = "X" } - if strProcessed == "y" { strProcessed = "Y" } - if strProcessed == "z" { strProcessed = "Z" } - } - case "com.apple.keylayout.ZhuyinEten": - do { - if strProcessed == "a" { strProcessed = "A" } - if strProcessed == "b" { strProcessed = "B" } - if strProcessed == "c" { strProcessed = "C" } - if strProcessed == "d" { strProcessed = "D" } - if strProcessed == "e" { strProcessed = "E" } - if strProcessed == "f" { strProcessed = "F" } - if strProcessed == "g" { strProcessed = "G" } - if strProcessed == "h" { strProcessed = "H" } - if strProcessed == "i" { strProcessed = "I" } - if strProcessed == "j" { strProcessed = "J" } - if strProcessed == "k" { strProcessed = "K" } - if strProcessed == "l" { strProcessed = "L" } - if strProcessed == "m" { strProcessed = "M" } - if strProcessed == "n" { strProcessed = "N" } - if strProcessed == "o" { strProcessed = "O" } - if strProcessed == "p" { strProcessed = "P" } - if strProcessed == "q" { strProcessed = "Q" } - if strProcessed == "r" { strProcessed = "R" } - if strProcessed == "s" { strProcessed = "S" } - if strProcessed == "t" { strProcessed = "T" } - if strProcessed == "u" { strProcessed = "U" } - if strProcessed == "v" { strProcessed = "V" } - if strProcessed == "w" { strProcessed = "W" } - if strProcessed == "x" { strProcessed = "X" } - if strProcessed == "y" { strProcessed = "Y" } - if strProcessed == "z" { strProcessed = "Z" } - } - default: break - } - // 注音鍵群。 - if strProcessed == "ㄝ" { strProcessed = "," } - if strProcessed == "ㄦ" { strProcessed = "-" } - if strProcessed == "ㄡ" { strProcessed = "." } - if strProcessed == "ㄥ" { strProcessed = "/" } - if strProcessed == "ㄢ" { strProcessed = "0" } - if strProcessed == "ㄅ" { strProcessed = "1" } - if strProcessed == "ㄉ" { strProcessed = "2" } - if strProcessed == "ˇ" { strProcessed = "3" } - if strProcessed == "ˋ" { strProcessed = "4" } - if strProcessed == "ㄓ" { strProcessed = "5" } - if strProcessed == "ˊ" { strProcessed = "6" } - if strProcessed == "˙" { strProcessed = "7" } - if strProcessed == "ㄚ" { strProcessed = "8" } - if strProcessed == "ㄞ" { strProcessed = "9" } - if strProcessed == "ㄤ" { strProcessed = ";" } - if strProcessed == "ㄇ" { strProcessed = "a" } - if strProcessed == "ㄖ" { strProcessed = "b" } - if strProcessed == "ㄏ" { strProcessed = "c" } - if strProcessed == "ㄎ" { strProcessed = "d" } - if strProcessed == "ㄍ" { strProcessed = "e" } - if strProcessed == "ㄑ" { strProcessed = "f" } - if strProcessed == "ㄕ" { strProcessed = "g" } - if strProcessed == "ㄘ" { strProcessed = "h" } - if strProcessed == "ㄛ" { strProcessed = "i" } - if strProcessed == "ㄨ" { strProcessed = "j" } - if strProcessed == "ㄜ" { strProcessed = "k" } - if strProcessed == "ㄠ" { strProcessed = "l" } - if strProcessed == "ㄩ" { strProcessed = "m" } - if strProcessed == "ㄙ" { strProcessed = "n" } - if strProcessed == "ㄟ" { strProcessed = "o" } - if strProcessed == "ㄣ" { strProcessed = "p" } - if strProcessed == "ㄆ" { strProcessed = "q" } - if strProcessed == "ㄐ" { strProcessed = "r" } - if strProcessed == "ㄋ" { strProcessed = "s" } - if strProcessed == "ㄔ" { strProcessed = "t" } - if strProcessed == "ㄧ" { strProcessed = "u" } - if strProcessed == "ㄒ" { strProcessed = "v" } - if strProcessed == "ㄊ" { strProcessed = "w" } - if strProcessed == "ㄌ" { strProcessed = "x" } - if strProcessed == "ㄗ" { strProcessed = "y" } - if strProcessed == "ㄈ" { strProcessed = "z" } - // 除了數字鍵區以外的標點符號。 - if strProcessed == "、" { strProcessed = "\\" } - if strProcessed == "「" { strProcessed = "[" } - if strProcessed == "」" { strProcessed = "]" } - if strProcessed == "『" { strProcessed = "{" } - if strProcessed == "』" { strProcessed = "}" } - if strProcessed == "," { strProcessed = "<" } - if strProcessed == "。" { strProcessed = ">" } - // 摁了 SHIFT 之後的數字區的符號。 - if strProcessed == "!" { strProcessed = "!" } - if strProcessed == "@" { strProcessed = "@" } - if strProcessed == "#" { strProcessed = "#" } - if strProcessed == "$" { strProcessed = "$" } - if strProcessed == "%" { strProcessed = "%" } - if strProcessed == "︿" { strProcessed = "^" } - if strProcessed == "&" { strProcessed = "&" } - if strProcessed == "*" { strProcessed = "*" } - if strProcessed == "(" { strProcessed = "(" } - if strProcessed == ")" { strProcessed = ")" } - // 摁了 Alt 的符號。 - if strProcessed == "—" { strProcessed = "-" } - // Apple 倚天注音佈局追加符號糾正項目。 - if mgrPrefs.basicKeyboardLayout == "com.apple.keylayout.ZhuyinEten" { - if strProcessed == "_" { strProcessed = "_" } - if strProcessed == ":" { strProcessed = ":" } - if strProcessed == "?" { strProcessed = "?" } - if strProcessed == "+" { strProcessed = "+" } - if strProcessed == "|" { strProcessed = "|" } - } - } - return strProcessed - } + class func cnvStringApple2ABC(_ strProcessed: String) -> String { + var strProcessed = strProcessed + if isDynamicBasicKeyboardLayoutEnabled() { + // 針對不同的 Apple 動態鍵盤佈局糾正大寫英文輸入。 + switch mgrPrefs.basicKeyboardLayout { + case "com.apple.keylayout.ZhuyinBopomofo": + do { + if strProcessed == "a" { strProcessed = "A" } + if strProcessed == "b" { strProcessed = "B" } + if strProcessed == "c" { strProcessed = "C" } + if strProcessed == "d" { strProcessed = "D" } + if strProcessed == "e" { strProcessed = "E" } + if strProcessed == "f" { strProcessed = "F" } + if strProcessed == "g" { strProcessed = "G" } + if strProcessed == "h" { strProcessed = "H" } + if strProcessed == "i" { strProcessed = "I" } + if strProcessed == "j" { strProcessed = "J" } + if strProcessed == "k" { strProcessed = "K" } + if strProcessed == "l" { strProcessed = "L" } + if strProcessed == "m" { strProcessed = "M" } + if strProcessed == "n" { strProcessed = "N" } + if strProcessed == "o" { strProcessed = "O" } + if strProcessed == "p" { strProcessed = "P" } + if strProcessed == "q" { strProcessed = "Q" } + if strProcessed == "r" { strProcessed = "R" } + if strProcessed == "s" { strProcessed = "S" } + if strProcessed == "t" { strProcessed = "T" } + if strProcessed == "u" { strProcessed = "U" } + if strProcessed == "v" { strProcessed = "V" } + if strProcessed == "w" { strProcessed = "W" } + if strProcessed == "x" { strProcessed = "X" } + if strProcessed == "y" { strProcessed = "Y" } + if strProcessed == "z" { strProcessed = "Z" } + } + case "com.apple.keylayout.ZhuyinEten": + do { + if strProcessed == "a" { strProcessed = "A" } + if strProcessed == "b" { strProcessed = "B" } + if strProcessed == "c" { strProcessed = "C" } + if strProcessed == "d" { strProcessed = "D" } + if strProcessed == "e" { strProcessed = "E" } + if strProcessed == "f" { strProcessed = "F" } + if strProcessed == "g" { strProcessed = "G" } + if strProcessed == "h" { strProcessed = "H" } + if strProcessed == "i" { strProcessed = "I" } + if strProcessed == "j" { strProcessed = "J" } + if strProcessed == "k" { strProcessed = "K" } + if strProcessed == "l" { strProcessed = "L" } + if strProcessed == "m" { strProcessed = "M" } + if strProcessed == "n" { strProcessed = "N" } + if strProcessed == "o" { strProcessed = "O" } + if strProcessed == "p" { strProcessed = "P" } + if strProcessed == "q" { strProcessed = "Q" } + if strProcessed == "r" { strProcessed = "R" } + if strProcessed == "s" { strProcessed = "S" } + if strProcessed == "t" { strProcessed = "T" } + if strProcessed == "u" { strProcessed = "U" } + if strProcessed == "v" { strProcessed = "V" } + if strProcessed == "w" { strProcessed = "W" } + if strProcessed == "x" { strProcessed = "X" } + if strProcessed == "y" { strProcessed = "Y" } + if strProcessed == "z" { strProcessed = "Z" } + } + default: break + } + // 注音鍵群。 + if strProcessed == "ㄝ" { strProcessed = "," } + if strProcessed == "ㄦ" { strProcessed = "-" } + if strProcessed == "ㄡ" { strProcessed = "." } + if strProcessed == "ㄥ" { strProcessed = "/" } + if strProcessed == "ㄢ" { strProcessed = "0" } + if strProcessed == "ㄅ" { strProcessed = "1" } + if strProcessed == "ㄉ" { strProcessed = "2" } + if strProcessed == "ˇ" { strProcessed = "3" } + if strProcessed == "ˋ" { strProcessed = "4" } + if strProcessed == "ㄓ" { strProcessed = "5" } + if strProcessed == "ˊ" { strProcessed = "6" } + if strProcessed == "˙" { strProcessed = "7" } + if strProcessed == "ㄚ" { strProcessed = "8" } + if strProcessed == "ㄞ" { strProcessed = "9" } + if strProcessed == "ㄤ" { strProcessed = ";" } + if strProcessed == "ㄇ" { strProcessed = "a" } + if strProcessed == "ㄖ" { strProcessed = "b" } + if strProcessed == "ㄏ" { strProcessed = "c" } + if strProcessed == "ㄎ" { strProcessed = "d" } + if strProcessed == "ㄍ" { strProcessed = "e" } + if strProcessed == "ㄑ" { strProcessed = "f" } + if strProcessed == "ㄕ" { strProcessed = "g" } + if strProcessed == "ㄘ" { strProcessed = "h" } + if strProcessed == "ㄛ" { strProcessed = "i" } + if strProcessed == "ㄨ" { strProcessed = "j" } + if strProcessed == "ㄜ" { strProcessed = "k" } + if strProcessed == "ㄠ" { strProcessed = "l" } + if strProcessed == "ㄩ" { strProcessed = "m" } + if strProcessed == "ㄙ" { strProcessed = "n" } + if strProcessed == "ㄟ" { strProcessed = "o" } + if strProcessed == "ㄣ" { strProcessed = "p" } + if strProcessed == "ㄆ" { strProcessed = "q" } + if strProcessed == "ㄐ" { strProcessed = "r" } + if strProcessed == "ㄋ" { strProcessed = "s" } + if strProcessed == "ㄔ" { strProcessed = "t" } + if strProcessed == "ㄧ" { strProcessed = "u" } + if strProcessed == "ㄒ" { strProcessed = "v" } + if strProcessed == "ㄊ" { strProcessed = "w" } + if strProcessed == "ㄌ" { strProcessed = "x" } + if strProcessed == "ㄗ" { strProcessed = "y" } + if strProcessed == "ㄈ" { strProcessed = "z" } + // 除了數字鍵區以外的標點符號。 + if strProcessed == "、" { strProcessed = "\\" } + if strProcessed == "「" { strProcessed = "[" } + if strProcessed == "」" { strProcessed = "]" } + if strProcessed == "『" { strProcessed = "{" } + if strProcessed == "』" { strProcessed = "}" } + if strProcessed == "," { strProcessed = "<" } + if strProcessed == "。" { strProcessed = ">" } + // 摁了 SHIFT 之後的數字區的符號。 + if strProcessed == "!" { strProcessed = "!" } + if strProcessed == "@" { strProcessed = "@" } + if strProcessed == "#" { strProcessed = "#" } + if strProcessed == "$" { strProcessed = "$" } + if strProcessed == "%" { strProcessed = "%" } + if strProcessed == "︿" { strProcessed = "^" } + if strProcessed == "&" { strProcessed = "&" } + if strProcessed == "*" { strProcessed = "*" } + if strProcessed == "(" { strProcessed = "(" } + if strProcessed == ")" { strProcessed = ")" } + // 摁了 Alt 的符號。 + if strProcessed == "—" { strProcessed = "-" } + // Apple 倚天注音佈局追加符號糾正項目。 + if mgrPrefs.basicKeyboardLayout == "com.apple.keylayout.ZhuyinEten" { + if strProcessed == "_" { strProcessed = "_" } + if strProcessed == ":" { strProcessed = ":" } + if strProcessed == "?" { strProcessed = "?" } + if strProcessed == "+" { strProcessed = "+" } + if strProcessed == "|" { strProcessed = "|" } + } + } + return strProcessed + } } diff --git a/Source/Modules/LanguageParsers/Gramambular/Gramambular.h b/Source/Modules/ControllerModules/CTools.h similarity index 72% rename from Source/Modules/LanguageParsers/Gramambular/Gramambular.h rename to Source/Modules/ControllerModules/CTools.h index d33a298b..3a246b3c 100644 --- a/Source/Modules/LanguageParsers/Gramambular/Gramambular.h +++ b/Source/Modules/ControllerModules/CTools.h @@ -1,6 +1,4 @@ -// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are of: -// (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -24,18 +22,12 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#ifndef GRAMAMBULAR_H_ -#define GRAMAMBULAR_H_ +#import -#include "Bigram.h" -#include "BlockReadingBuilder.h" -#include "Grid.h" -#include "KeyValuePair.h" -#include "LanguageModel.h" -#include "Node.h" -#include "NodeAnchor.h" -#include "Span.h" -#include "Unigram.h" -#include "Walker.h" +NS_ASSUME_NONNULL_BEGIN -#endif +@interface CTools : NSObject ++ (BOOL)isPrintable:(UniChar)charCode; +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/Modules/FileHandlers/LMConsolidator.h b/Source/Modules/ControllerModules/CTools.m similarity index 71% rename from Source/Modules/FileHandlers/LMConsolidator.h rename to Source/Modules/ControllerModules/CTools.m index 9bda0d9e..97662c9b 100644 --- a/Source/Modules/FileHandlers/LMConsolidator.h +++ b/Source/Modules/ControllerModules/CTools.m @@ -22,30 +22,11 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#ifndef LMConsolidator_hpp -#define LMConsolidator_hpp +#import "CTools.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace std; -namespace vChewing +@implementation CTools ++ (BOOL)isPrintable:(UniChar)charCode { - -class LMConsolidator -{ - public: - static bool CheckPragma(const char *path); - static bool FixEOF(const char *path); - static bool ConsolidateContent(const char *path, bool shouldCheckPragma); -}; - -} // namespace vChewing -#endif /* LMConsolidator_hpp */ + return isprint(charCode); +} +@end diff --git a/Source/Modules/ControllerModules/InputHandler.swift b/Source/Modules/ControllerModules/InputHandler.swift index 51085d84..66de0ba7 100644 --- a/Source/Modules/ControllerModules/InputHandler.swift +++ b/Source/Modules/ControllerModules/InputHandler.swift @@ -29,301 +29,301 @@ import Cocoa // Use KeyCodes as much as possible since its recognition won't be affected by macOS Base Keyboard Layouts. // KeyCodes: https://eastmanreference.com/complete-list-of-applescript-key-codes // Also: HIToolbox.framework/Versions/A/Headers/Events.h -@objc enum KeyCode: UInt16 { - case kNone = 0 - case kCarriageReturn = 36 // Renamed from "kReturn" to avoid nomenclatural confusions. - case kTab = 48 - case kSpace = 49 - case kSymbolMenuPhysicalKey = 50 // vChewing Specific - case kBackSpace = 51 // Renamed from "kDelete" to avoid nomenclatural confusions. - case kEscape = 53 - case kCommand = 55 - case kShift = 56 - case kCapsLock = 57 - case kOption = 58 - case kControl = 59 - case kRightShift = 60 - case kRightOption = 61 - case kRightControl = 62 - case kFunction = 63 - case kF17 = 64 - case kVolumeUp = 72 - case kVolumeDown = 73 - case kMute = 74 - case kLineFeed = 76 // Another keyCode to identify the Enter Key. - case kF18 = 79 - case kF19 = 80 - case kF20 = 90 - case kF5 = 96 - case kF6 = 97 - case kF7 = 98 - case kF3 = 99 - case kF8 = 100 - case kF9 = 101 - case kF11 = 103 - case kF13 = 105 - case kF16 = 106 - case kF14 = 107 - case kF10 = 109 - case kF12 = 111 - case kF15 = 113 - case kHelp = 114 - case kHome = 115 - case kPageUp = 116 - case kWindowDelete = 117 // Renamed from "kForwardDelete" to avoid nomenclatural confusions. - case kF4 = 118 - case kEnd = 119 - case kF2 = 120 - case kPageDown = 121 - case kF1 = 122 - case kLeftArrow = 123 - case kRightArrow = 124 - case kDownArrow = 125 - case kUpArrow = 126 +enum KeyCode: UInt16 { + case kNone = 0 + case kCarriageReturn = 36 // Renamed from "kReturn" to avoid nomenclatural confusions. + case kTab = 48 + case kSpace = 49 + case kSymbolMenuPhysicalKey = 50 // vChewing Specific + case kBackSpace = 51 // Renamed from "kDelete" to avoid nomenclatural confusions. + case kEscape = 53 + case kCommand = 55 + case kShift = 56 + case kCapsLock = 57 + case kOption = 58 + case kControl = 59 + case kRightShift = 60 + case kRightOption = 61 + case kRightControl = 62 + case kFunction = 63 + case kF17 = 64 + case kVolumeUp = 72 + case kVolumeDown = 73 + case kMute = 74 + case kLineFeed = 76 // Another keyCode to identify the Enter Key. + case kF18 = 79 + case kF19 = 80 + case kF20 = 90 + case kF5 = 96 + case kF6 = 97 + case kF7 = 98 + case kF3 = 99 + case kF8 = 100 + case kF9 = 101 + case kF11 = 103 + case kF13 = 105 + case kF16 = 106 + case kF14 = 107 + case kF10 = 109 + case kF12 = 111 + case kF15 = 113 + case kHelp = 114 + case kHome = 115 + case kPageUp = 116 + case kWindowDelete = 117 // Renamed from "kForwardDelete" to avoid nomenclatural confusions. + case kF4 = 118 + case kEnd = 119 + case kF2 = 120 + case kPageDown = 121 + case kF1 = 122 + case kLeftArrow = 123 + case kRightArrow = 124 + case kDownArrow = 125 + case kUpArrow = 126 } // CharCodes: https://theasciicode.com.ar/ascii-control-characters/horizontal-tab-ascii-code-9.html enum CharCode: UInt /* 16 */ { - case yajuusenpai = 114_514_191_191_810_893 - // CharCode is not reliable at all. KeyCode is the most appropriate choice due to its accuracy. - // KeyCode doesn't give a phuque about the character sent through macOS keyboard layouts ... - // ... but only focuses on which physical key is pressed. + case yajuusenpai = 114_514_191_191_810_893 + // CharCode is not reliable at all. KeyCode is the most appropriate choice due to its accuracy. + // KeyCode doesn't give a phuque about the character sent through macOS keyboard layouts ... + // ... but only focuses on which physical key is pressed. } class InputHandler: NSObject { - @objc private(set) var useVerticalMode: Bool - @objc private(set) var inputText: String? - @objc private(set) var inputTextIgnoringModifiers: String? - @objc private(set) var charCode: UInt16 - @objc private(set) var keyCode: UInt16 - private var isFlagChanged: Bool - private var flags: NSEvent.ModifierFlags - private var cursorForwardKey: KeyCode - private var cursorBackwardKey: KeyCode - private var extraChooseCandidateKey: KeyCode - private var extraChooseCandidateKeyReverse: KeyCode - private var absorbedArrowKey: KeyCode - private var verticalModeOnlyChooseCandidateKey: KeyCode - @objc private(set) var emacsKey: vChewingEmacsKey + private(set) var useVerticalMode: Bool + private(set) var inputText: String? + private(set) var inputTextIgnoringModifiers: String? + private(set) var charCode: UInt16 + private(set) var keyCode: UInt16 + private var isFlagChanged: Bool + private var flags: NSEvent.ModifierFlags + private var cursorForwardKey: KeyCode + private var cursorBackwardKey: KeyCode + private var extraChooseCandidateKey: KeyCode + private var extraChooseCandidateKeyReverse: KeyCode + private var absorbedArrowKey: KeyCode + private var verticalModeOnlyChooseCandidateKey: KeyCode + private(set) var emacsKey: vChewingEmacsKey - @objc init( - inputText: String?, keyCode: UInt16, charCode: UInt16, flags: NSEvent.ModifierFlags, - isVerticalMode: Bool, inputTextIgnoringModifiers: String? = nil - ) { - let inputText = AppleKeyboardConverter.cnvStringApple2ABC(inputText ?? "") - let inputTextIgnoringModifiers = AppleKeyboardConverter.cnvStringApple2ABC( - inputTextIgnoringModifiers ?? inputText) - self.inputText = inputText - self.inputTextIgnoringModifiers = inputTextIgnoringModifiers - self.flags = flags - isFlagChanged = false - useVerticalMode = isVerticalMode - self.keyCode = keyCode - self.charCode = AppleKeyboardConverter.cnvApple2ABC(charCode) - emacsKey = EmacsKeyHelper.detect( - charCode: AppleKeyboardConverter.cnvApple2ABC(charCode), flags: flags - ) - // Define Arrow Keys - cursorForwardKey = useVerticalMode ? .kDownArrow : .kRightArrow - cursorBackwardKey = useVerticalMode ? .kUpArrow : .kLeftArrow - extraChooseCandidateKey = useVerticalMode ? .kLeftArrow : .kDownArrow - extraChooseCandidateKeyReverse = useVerticalMode ? .kRightArrow : .kUpArrow - absorbedArrowKey = useVerticalMode ? .kRightArrow : .kUpArrow - verticalModeOnlyChooseCandidateKey = useVerticalMode ? absorbedArrowKey : .kNone - super.init() - } + init( + inputText: String?, keyCode: UInt16, charCode: UInt16, flags: NSEvent.ModifierFlags, + isVerticalMode: Bool, inputTextIgnoringModifiers: String? = nil + ) { + let inputText = AppleKeyboardConverter.cnvStringApple2ABC(inputText ?? "") + let inputTextIgnoringModifiers = AppleKeyboardConverter.cnvStringApple2ABC( + inputTextIgnoringModifiers ?? inputText) + self.inputText = inputText + self.inputTextIgnoringModifiers = inputTextIgnoringModifiers + self.flags = flags + isFlagChanged = false + useVerticalMode = isVerticalMode + self.keyCode = keyCode + self.charCode = AppleKeyboardConverter.cnvApple2ABC(charCode) + emacsKey = EmacsKeyHelper.detect( + charCode: AppleKeyboardConverter.cnvApple2ABC(charCode), flags: flags + ) + // Define Arrow Keys + cursorForwardKey = useVerticalMode ? .kDownArrow : .kRightArrow + cursorBackwardKey = useVerticalMode ? .kUpArrow : .kLeftArrow + extraChooseCandidateKey = useVerticalMode ? .kLeftArrow : .kDownArrow + extraChooseCandidateKeyReverse = useVerticalMode ? .kRightArrow : .kUpArrow + absorbedArrowKey = useVerticalMode ? .kRightArrow : .kUpArrow + verticalModeOnlyChooseCandidateKey = useVerticalMode ? absorbedArrowKey : .kNone + super.init() + } - @objc init(event: NSEvent, isVerticalMode: Bool) { - inputText = AppleKeyboardConverter.cnvStringApple2ABC(event.characters ?? "") - inputTextIgnoringModifiers = AppleKeyboardConverter.cnvStringApple2ABC( - event.charactersIgnoringModifiers ?? "") - keyCode = event.keyCode - flags = event.modifierFlags - isFlagChanged = (event.type == .flagsChanged) ? true : false - useVerticalMode = isVerticalMode - let charCode: UInt16 = { - // 這裡不用「count > 0」,因為該整數變數只要「!isEmpty」那就必定滿足這個條件。 - guard let inputText = event.characters, !inputText.isEmpty else { - return 0 - } - let first = inputText[inputText.startIndex].utf16.first! - return first - }() - self.charCode = AppleKeyboardConverter.cnvApple2ABC(charCode) - emacsKey = EmacsKeyHelper.detect( - charCode: AppleKeyboardConverter.cnvApple2ABC(charCode), flags: flags - ) - // Define Arrow Keys in the same way above. - cursorForwardKey = useVerticalMode ? .kDownArrow : .kRightArrow - cursorBackwardKey = useVerticalMode ? .kUpArrow : .kLeftArrow - extraChooseCandidateKey = useVerticalMode ? .kLeftArrow : .kDownArrow - extraChooseCandidateKeyReverse = useVerticalMode ? .kRightArrow : .kUpArrow - absorbedArrowKey = useVerticalMode ? .kRightArrow : .kUpArrow - verticalModeOnlyChooseCandidateKey = useVerticalMode ? absorbedArrowKey : .kNone - super.init() - } + init(event: NSEvent, isVerticalMode: Bool) { + inputText = AppleKeyboardConverter.cnvStringApple2ABC(event.characters ?? "") + inputTextIgnoringModifiers = AppleKeyboardConverter.cnvStringApple2ABC( + event.charactersIgnoringModifiers ?? "") + keyCode = event.keyCode + flags = event.modifierFlags + isFlagChanged = (event.type == .flagsChanged) ? true : false + useVerticalMode = isVerticalMode + let charCode: UInt16 = { + // 這裡不用「count > 0」,因為該整數變數只要「!isEmpty」那就必定滿足這個條件。 + guard let inputText = event.characters, !inputText.isEmpty else { + return 0 + } + let first = inputText[inputText.startIndex].utf16.first! + return first + }() + self.charCode = AppleKeyboardConverter.cnvApple2ABC(charCode) + emacsKey = EmacsKeyHelper.detect( + charCode: AppleKeyboardConverter.cnvApple2ABC(charCode), flags: flags + ) + // Define Arrow Keys in the same way above. + cursorForwardKey = useVerticalMode ? .kDownArrow : .kRightArrow + cursorBackwardKey = useVerticalMode ? .kUpArrow : .kLeftArrow + extraChooseCandidateKey = useVerticalMode ? .kLeftArrow : .kDownArrow + extraChooseCandidateKeyReverse = useVerticalMode ? .kRightArrow : .kUpArrow + absorbedArrowKey = useVerticalMode ? .kRightArrow : .kUpArrow + verticalModeOnlyChooseCandidateKey = useVerticalMode ? absorbedArrowKey : .kNone + super.init() + } - override var description: String { - charCode = AppleKeyboardConverter.cnvApple2ABC(charCode) - inputText = AppleKeyboardConverter.cnvStringApple2ABC(inputText ?? "") - inputTextIgnoringModifiers = AppleKeyboardConverter.cnvStringApple2ABC( - inputTextIgnoringModifiers ?? "") - return - "<\(super.description) inputText:\(String(describing: inputText)), inputTextIgnoringModifiers:\(String(describing: inputTextIgnoringModifiers)) charCode:\(charCode), keyCode:\(keyCode), flags:\(flags), cursorForwardKey:\(cursorForwardKey), cursorBackwardKey:\(cursorBackwardKey), extraChooseCandidateKey:\(extraChooseCandidateKey), extraChooseCandidateKeyReverse:\(extraChooseCandidateKeyReverse), absorbedArrowKey:\(absorbedArrowKey), verticalModeOnlyChooseCandidateKey:\(verticalModeOnlyChooseCandidateKey), emacsKey:\(emacsKey), useVerticalMode:\(useVerticalMode)>" - } + override var description: String { + charCode = AppleKeyboardConverter.cnvApple2ABC(charCode) + inputText = AppleKeyboardConverter.cnvStringApple2ABC(inputText ?? "") + inputTextIgnoringModifiers = AppleKeyboardConverter.cnvStringApple2ABC( + inputTextIgnoringModifiers ?? "") + return + "<\(super.description) inputText:\(String(describing: inputText)), inputTextIgnoringModifiers:\(String(describing: inputTextIgnoringModifiers)) charCode:\(charCode), keyCode:\(keyCode), flags:\(flags), cursorForwardKey:\(cursorForwardKey), cursorBackwardKey:\(cursorBackwardKey), extraChooseCandidateKey:\(extraChooseCandidateKey), extraChooseCandidateKeyReverse:\(extraChooseCandidateKeyReverse), absorbedArrowKey:\(absorbedArrowKey), verticalModeOnlyChooseCandidateKey:\(verticalModeOnlyChooseCandidateKey), emacsKey:\(emacsKey), useVerticalMode:\(useVerticalMode)>" + } - @objc var isShiftHold: Bool { - flags.contains([.shift]) - } + var isShiftHold: Bool { + flags.contains([.shift]) + } - @objc var isCommandHold: Bool { - flags.contains([.command]) - } + var isCommandHold: Bool { + flags.contains([.command]) + } - @objc var isControlHold: Bool { - flags.contains([.control]) - } + var isControlHold: Bool { + flags.contains([.control]) + } - @objc var isControlHotKey: Bool { - flags.contains([.control]) && inputText?.first?.isLetter ?? false - } + var isControlHotKey: Bool { + flags.contains([.control]) && inputText?.first?.isLetter ?? false + } - @objc var isOptionHotKey: Bool { - flags.contains([.option]) && inputText?.first?.isLetter ?? false - } + var isOptionHotKey: Bool { + flags.contains([.option]) && inputText?.first?.isLetter ?? false + } - @objc var isOptionHold: Bool { - flags.contains([.option]) - } + var isOptionHold: Bool { + flags.contains([.option]) + } - @objc var isCapsLockOn: Bool { - flags.contains([.capsLock]) - } + var isCapsLockOn: Bool { + flags.contains([.capsLock]) + } - @objc var isNumericPad: Bool { - flags.contains([.numericPad]) - } + var isNumericPad: Bool { + flags.contains([.numericPad]) + } - @objc var isFunctionKeyHold: Bool { - flags.contains([.function]) - } + var isFunctionKeyHold: Bool { + flags.contains([.function]) + } - @objc var isReservedKey: Bool { - guard let code = KeyCode(rawValue: keyCode) else { - return false - } - return code.rawValue != KeyCode.kNone.rawValue - } + var isReservedKey: Bool { + guard let code = KeyCode(rawValue: keyCode) else { + return false + } + return code.rawValue != KeyCode.kNone.rawValue + } - @objc var isTab: Bool { - KeyCode(rawValue: keyCode) == KeyCode.kTab - } + var isTab: Bool { + KeyCode(rawValue: keyCode) == KeyCode.kTab + } - @objc var isEnter: Bool { - (KeyCode(rawValue: keyCode) == KeyCode.kCarriageReturn) - || (KeyCode(rawValue: keyCode) == KeyCode.kLineFeed) - } + var isEnter: Bool { + (KeyCode(rawValue: keyCode) == KeyCode.kCarriageReturn) + || (KeyCode(rawValue: keyCode) == KeyCode.kLineFeed) + } - @objc var isUp: Bool { - KeyCode(rawValue: keyCode) == KeyCode.kUpArrow - } + var isUp: Bool { + KeyCode(rawValue: keyCode) == KeyCode.kUpArrow + } - @objc var isDown: Bool { - KeyCode(rawValue: keyCode) == KeyCode.kDownArrow - } + var isDown: Bool { + KeyCode(rawValue: keyCode) == KeyCode.kDownArrow + } - @objc var isLeft: Bool { - KeyCode(rawValue: keyCode) == KeyCode.kLeftArrow - } + var isLeft: Bool { + KeyCode(rawValue: keyCode) == KeyCode.kLeftArrow + } - @objc var isRight: Bool { - KeyCode(rawValue: keyCode) == KeyCode.kRightArrow - } + var isRight: Bool { + KeyCode(rawValue: keyCode) == KeyCode.kRightArrow + } - @objc var isPageUp: Bool { - KeyCode(rawValue: keyCode) == KeyCode.kPageUp - } + var isPageUp: Bool { + KeyCode(rawValue: keyCode) == KeyCode.kPageUp + } - @objc var isPageDown: Bool { - KeyCode(rawValue: keyCode) == KeyCode.kPageDown - } + var isPageDown: Bool { + KeyCode(rawValue: keyCode) == KeyCode.kPageDown + } - @objc var isSpace: Bool { - KeyCode(rawValue: keyCode) == KeyCode.kSpace - } + var isSpace: Bool { + KeyCode(rawValue: keyCode) == KeyCode.kSpace + } - @objc var isBackSpace: Bool { - KeyCode(rawValue: keyCode) == KeyCode.kBackSpace - } + var isBackSpace: Bool { + KeyCode(rawValue: keyCode) == KeyCode.kBackSpace + } - @objc var isESC: Bool { - KeyCode(rawValue: keyCode) == KeyCode.kEscape - } + var isESC: Bool { + KeyCode(rawValue: keyCode) == KeyCode.kEscape + } - @objc var isHome: Bool { - KeyCode(rawValue: keyCode) == KeyCode.kHome - } + var isHome: Bool { + KeyCode(rawValue: keyCode) == KeyCode.kHome + } - @objc var isEnd: Bool { - KeyCode(rawValue: keyCode) == KeyCode.kEnd - } + var isEnd: Bool { + KeyCode(rawValue: keyCode) == KeyCode.kEnd + } - @objc var isDelete: Bool { - KeyCode(rawValue: keyCode) == KeyCode.kWindowDelete - } + var isDelete: Bool { + KeyCode(rawValue: keyCode) == KeyCode.kWindowDelete + } - @objc var isCursorBackward: Bool { - KeyCode(rawValue: keyCode) == cursorBackwardKey - } + var isCursorBackward: Bool { + KeyCode(rawValue: keyCode) == cursorBackwardKey + } - @objc var isCursorForward: Bool { - KeyCode(rawValue: keyCode) == cursorForwardKey - } + var isCursorForward: Bool { + KeyCode(rawValue: keyCode) == cursorForwardKey + } - @objc var isAbsorbedArrowKey: Bool { - KeyCode(rawValue: keyCode) == absorbedArrowKey - } + var isAbsorbedArrowKey: Bool { + KeyCode(rawValue: keyCode) == absorbedArrowKey + } - @objc var isExtraChooseCandidateKey: Bool { - KeyCode(rawValue: keyCode) == extraChooseCandidateKey - } + var isExtraChooseCandidateKey: Bool { + KeyCode(rawValue: keyCode) == extraChooseCandidateKey + } - @objc var isExtraChooseCandidateKeyReverse: Bool { - KeyCode(rawValue: keyCode) == extraChooseCandidateKeyReverse - } + var isExtraChooseCandidateKeyReverse: Bool { + KeyCode(rawValue: keyCode) == extraChooseCandidateKeyReverse + } - @objc var isVerticalModeOnlyChooseCandidateKey: Bool { - KeyCode(rawValue: keyCode) == verticalModeOnlyChooseCandidateKey - } + var isVerticalModeOnlyChooseCandidateKey: Bool { + KeyCode(rawValue: keyCode) == verticalModeOnlyChooseCandidateKey + } - @objc var isUpperCaseASCIILetterKey: Bool { - // 這裡必須加上「flags == .shift」,否則會出現某些情況下輸入法「誤判當前鍵入的非 Shift 字符為大寫」的問題。 - charCode >= 65 && charCode <= 90 && flags == .shift - } + var isUpperCaseASCIILetterKey: Bool { + // 這裡必須加上「flags == .shift」,否則會出現某些情況下輸入法「誤判當前鍵入的非 Shift 字符為大寫」的問題。 + charCode >= 65 && charCode <= 90 && flags == .shift + } - @objc var isSymbolMenuPhysicalKey: Bool { - // 這裡必須用 KeyCode,這樣才不會受隨 macOS 版本更動的 Apple 動態注音鍵盤排列內容的影響。 - // 只是必須得與 ![input isShift] 搭配使用才可以(也就是僅判定 Shift 沒被摁下的情形)。 - KeyCode(rawValue: keyCode) == KeyCode.kSymbolMenuPhysicalKey - } + var isSymbolMenuPhysicalKey: Bool { + // 這裡必須用 KeyCode,這樣才不會受隨 macOS 版本更動的 Apple 動態注音鍵盤排列內容的影響。 + // 只是必須得與 ![input isShift] 搭配使用才可以(也就是僅判定 Shift 沒被摁下的情形)。 + KeyCode(rawValue: keyCode) == KeyCode.kSymbolMenuPhysicalKey + } } -@objc enum vChewingEmacsKey: UInt16 { - case none = 0 - case forward = 6 // F - case backward = 2 // B - case home = 1 // A - case end = 5 // E - case delete = 4 // D - case nextPage = 22 // V +enum vChewingEmacsKey: UInt16 { + case none = 0 + case forward = 6 // F + case backward = 2 // B + case home = 1 // A + case end = 5 // E + case delete = 4 // D + case nextPage = 22 // V } class EmacsKeyHelper: NSObject { - @objc static func detect(charCode: UniChar, flags: NSEvent.ModifierFlags) -> vChewingEmacsKey { - let charCode = AppleKeyboardConverter.cnvApple2ABC(charCode) - if flags.contains(.control) { - return vChewingEmacsKey(rawValue: charCode) ?? .none - } - return .none - } + static func detect(charCode: UniChar, flags: NSEvent.ModifierFlags) -> vChewingEmacsKey { + let charCode = AppleKeyboardConverter.cnvApple2ABC(charCode) + if flags.contains(.control) { + return vChewingEmacsKey(rawValue: charCode) ?? .none + } + return .none + } } diff --git a/Source/Modules/ControllerModules/InputState.swift b/Source/Modules/ControllerModules/InputState.swift index 25a9de19..f03ecda2 100644 --- a/Source/Modules/ControllerModules/InputState.swift +++ b/Source/Modules/ControllerModules/InputState.swift @@ -58,449 +58,439 @@ import Cocoa /// - Choosing Candidate: The candidate window is open to let the user to choose /// one among the candidates. class InputState: NSObject { - /// Represents that the input controller is deactivated. - @objc(InputStateDeactivated) - class Deactivated: InputState { - override var description: String { - "" - } - } + /// Represents that the input controller is deactivated. + class Deactivated: InputState { + override var description: String { + "" + } + } - // MARK: - + // MARK: - - /// Represents that the composing buffer is empty. - @objc(InputStateEmpty) - class Empty: InputState { - @objc var composingBuffer: String { - "" - } + /// Represents that the composing buffer is empty. + class Empty: InputState { + var composingBuffer: String { + "" + } - override var description: String { - "" - } - } + override var description: String { + "" + } + } - // MARK: - + // MARK: - - /// Represents that the composing buffer is empty. - @objc(InputStateEmptyIgnoringPreviousState) - class EmptyIgnoringPreviousState: InputState { - @objc var composingBuffer: String { - "" - } + /// Represents that the composing buffer is empty. + class EmptyIgnoringPreviousState: InputState { + var composingBuffer: String { + "" + } - override var description: String { - "" - } - } + override var description: String { + "" + } + } - // MARK: - + // MARK: - - /// Represents that the input controller is committing text into client app. - @objc(InputStateCommitting) - class Committing: InputState { - @objc private(set) var poppedText: String = "" + /// Represents that the input controller is committing text into client app. + class Committing: InputState { + private(set) var poppedText: String = "" - @objc convenience init(poppedText: String) { - self.init() - self.poppedText = poppedText - } + convenience init(poppedText: String) { + self.init() + self.poppedText = poppedText + } - override var description: String { - "" - } - } + override var description: String { + "" + } + } - // MARK: - + // MARK: - - /// Represents that the composing buffer is not empty. - @objc(InputStateNotEmpty) - class NotEmpty: InputState { - @objc private(set) var composingBuffer: String - @objc private(set) var cursorIndex: UInt + /// Represents that the composing buffer is not empty. + class NotEmpty: InputState { + private(set) var composingBuffer: String + private(set) var cursorIndex: UInt - @objc init(composingBuffer: String, cursorIndex: UInt) { - self.composingBuffer = composingBuffer - self.cursorIndex = cursorIndex - } + init(composingBuffer: String, cursorIndex: UInt) { + self.composingBuffer = composingBuffer + self.cursorIndex = cursorIndex + } - override var description: String { - "" - } - } + override var description: String { + "" + } + } - // MARK: - + // MARK: - - /// Represents that the user is inputting text. - @objc(InputStateInputting) - class Inputting: NotEmpty { - @objc var poppedText: String = "" - @objc var tooltip: String = "" + /// Represents that the user is inputting text. + class Inputting: NotEmpty { + var poppedText: String = "" + var tooltip: String = "" - @objc override init(composingBuffer: String, cursorIndex: UInt) { - super.init(composingBuffer: composingBuffer, cursorIndex: cursorIndex) - } + override init(composingBuffer: String, cursorIndex: UInt) { + super.init(composingBuffer: composingBuffer, cursorIndex: cursorIndex) + } - @objc var attributedString: NSAttributedString { - let attributedSting = NSAttributedString( - string: composingBuffer, - attributes: [ - .underlineStyle: NSUnderlineStyle.single.rawValue, - .markedClauseSegment: 0, - ] - ) - return attributedSting - } + var attributedString: NSAttributedString { + let attributedSting = NSAttributedString( + string: composingBuffer, + attributes: [ + .underlineStyle: NSUnderlineStyle.single.rawValue, + .markedClauseSegment: 0, + ] + ) + return attributedSting + } - override var description: String { - ", poppedText:\(poppedText)>" - } - } + override var description: String { + ", poppedText:\(poppedText)>" + } + } - // MARK: - + // MARK: - - private let kMinMarkRangeLength = 2 - private let kMaxMarkRangeLength = mgrPrefs.maxCandidateLength + private let kMinMarkRangeLength = 2 + private let kMaxMarkRangeLength = mgrPrefs.maxCandidateLength - /// Represents that the user is marking a range in the composing buffer. - @objc(InputStateMarking) - class Marking: NotEmpty { - @objc private(set) var markerIndex: UInt - @objc private(set) var markedRange: NSRange - @objc private var deleteTargetExists = false - @objc var tooltip: String { - if composingBuffer.count != readings.count { - TooltipController.backgroundColor = NSColor( - red: 0.55, green: 0.00, blue: 0.00, alpha: 1.00 - ) - TooltipController.textColor = NSColor.white - return NSLocalizedString( - "⚠︎ Unhandlable: Chars and Readings in buffer doesn't match.", comment: "" - ) - } + /// Represents that the user is marking a range in the composing buffer. + class Marking: NotEmpty { + private(set) var markerIndex: UInt + private(set) var markedRange: NSRange + private var deleteTargetExists = false + var tooltip: String { + if composingBuffer.count != readings.count { + TooltipController.backgroundColor = NSColor( + red: 0.55, green: 0.00, blue: 0.00, alpha: 1.00 + ) + TooltipController.textColor = NSColor.white + return NSLocalizedString( + "⚠︎ Unhandlable: Chars and Readings in buffer doesn't match.", comment: "" + ) + } - if mgrPrefs.phraseReplacementEnabled { - TooltipController.backgroundColor = NSColor.purple - TooltipController.textColor = NSColor.white - return NSLocalizedString( - "⚠︎ Phrase replacement mode enabled, interfering user phrase entry.", comment: "" - ) - } - if markedRange.length == 0 { - return "" - } + if mgrPrefs.phraseReplacementEnabled { + TooltipController.backgroundColor = NSColor.purple + TooltipController.textColor = NSColor.white + return NSLocalizedString( + "⚠︎ Phrase replacement mode enabled, interfering user phrase entry.", comment: "" + ) + } + if markedRange.length == 0 { + return "" + } - let text = (composingBuffer as NSString).substring(with: markedRange) - if markedRange.length < kMinMarkRangeLength { - TooltipController.backgroundColor = NSColor( - red: 0.18, green: 0.18, blue: 0.18, alpha: 1.00 - ) - TooltipController.textColor = NSColor( - red: 0.86, green: 0.86, blue: 0.86, alpha: 1.00 - ) - return String( - format: NSLocalizedString( - "\"%@\" length must ≥ 2 for a user phrase.", comment: "" - ), text - ) - } else if markedRange.length > kMaxMarkRangeLength { - TooltipController.backgroundColor = NSColor( - red: 0.26, green: 0.16, blue: 0.00, alpha: 1.00 - ) - TooltipController.textColor = NSColor( - red: 1.00, green: 0.60, blue: 0.00, alpha: 1.00 - ) - return String( - format: NSLocalizedString( - "\"%@\" length should ≤ %d for a user phrase.", comment: "" - ), - text, kMaxMarkRangeLength - ) - } + let text = (composingBuffer as NSString).substring(with: markedRange) + if markedRange.length < kMinMarkRangeLength { + TooltipController.backgroundColor = NSColor( + red: 0.18, green: 0.18, blue: 0.18, alpha: 1.00 + ) + TooltipController.textColor = NSColor( + red: 0.86, green: 0.86, blue: 0.86, alpha: 1.00 + ) + return String( + format: NSLocalizedString( + "\"%@\" length must ≥ 2 for a user phrase.", comment: "" + ), text + ) + } else if markedRange.length > kMaxMarkRangeLength { + TooltipController.backgroundColor = NSColor( + red: 0.26, green: 0.16, blue: 0.00, alpha: 1.00 + ) + TooltipController.textColor = NSColor( + red: 1.00, green: 0.60, blue: 0.00, alpha: 1.00 + ) + return String( + format: NSLocalizedString( + "\"%@\" length should ≤ %d for a user phrase.", comment: "" + ), + text, kMaxMarkRangeLength + ) + } - let (exactBegin, _) = (composingBuffer as NSString).characterIndex( - from: markedRange.location) - let (exactEnd, _) = (composingBuffer as NSString).characterIndex( - from: markedRange.location + markedRange.length) - let selectedReadings = readings[exactBegin.." - } + override var description: String { + "" + } - @objc func convertToInputting() -> Inputting { - let state = Inputting(composingBuffer: composingBuffer, cursorIndex: cursorIndex) - state.tooltip = tooltipForInputting - return state - } + func convertToInputting() -> Inputting { + let state = Inputting(composingBuffer: composingBuffer, cursorIndex: cursorIndex) + state.tooltip = tooltipForInputting + return state + } - @objc var validToWrite: Bool { - /// vChewing allows users to input a string whose length differs - /// from the amount of Bopomofo readings. In this case, the range - /// in the composing buffer and the readings could not match, so - /// we disable the function to write user phrases in this case. - if composingBuffer.count != readings.count { - return false - } - if markedRange.length < kMinMarkRangeLength { - return false - } - if markedRange.length > kMaxMarkRangeLength { - return false - } - if ctlInputMethod.areWeDeleting, !deleteTargetExists { - return false - } - return markedRange.length >= kMinMarkRangeLength - && markedRange.length <= kMaxMarkRangeLength - } + var validToWrite: Bool { + /// vChewing allows users to input a string whose length differs + /// from the amount of Bopomofo readings. In this case, the range + /// in the composing buffer and the readings could not match, so + /// we disable the function to write user phrases in this case. + if composingBuffer.count != readings.count { + return false + } + if markedRange.length < kMinMarkRangeLength { + return false + } + if markedRange.length > kMaxMarkRangeLength { + return false + } + if ctlInputMethod.areWeDeleting, !deleteTargetExists { + return false + } + return markedRange.length >= kMinMarkRangeLength + && markedRange.length <= kMaxMarkRangeLength + } - @objc var chkIfUserPhraseExists: Bool { - let text = (composingBuffer as NSString).substring(with: markedRange) - let (exactBegin, _) = (composingBuffer as NSString).characterIndex( - from: markedRange.location) - let (exactEnd, _) = (composingBuffer as NSString).characterIndex( - from: markedRange.location + markedRange.length) - let selectedReadings = readings[exactBegin.." - } - } + override var description: String { + "" + } + } - // MARK: - + // MARK: - - /// Represents that the user is choosing in a candidates list - /// in the associated phrases mode. - @objc(InputStateAssociatedPhrases) - class AssociatedPhrases: InputState { - @objc private(set) var candidates: [String] = [] - @objc private(set) var useVerticalMode: Bool = false - @objc init(candidates: [String], useVerticalMode: Bool) { - self.candidates = candidates - self.useVerticalMode = useVerticalMode - super.init() - } + /// Represents that the user is choosing in a candidates list + /// in the associated phrases mode. + class AssociatedPhrases: InputState { + private(set) var candidates: [String] = [] + private(set) var useVerticalMode: Bool = false + init(candidates: [String], useVerticalMode: Bool) { + self.candidates = candidates + self.useVerticalMode = useVerticalMode + super.init() + } - override var description: String { - "" - } - } + override var description: String { + "" + } + } - @objc(InputStateSymbolTable) - class SymbolTable: ChoosingCandidate { - @objc var node: SymbolNode + class SymbolTable: ChoosingCandidate { + var node: SymbolNode - @objc init(node: SymbolNode, useVerticalMode: Bool) { - self.node = node - let candidates = node.children?.map(\.title) ?? [String]() - super.init( - composingBuffer: "", cursorIndex: 0, candidates: candidates, - useVerticalMode: useVerticalMode - ) - } + init(node: SymbolNode, useVerticalMode: Bool) { + self.node = node + let candidates = node.children?.map(\.title) ?? [String]() + super.init( + composingBuffer: "", cursorIndex: 0, candidates: candidates, + useVerticalMode: useVerticalMode + ) + } - override var description: String { - "" - } - } + override var description: String { + "" + } + } } class SymbolNode: NSObject { - @objc var title: String - @objc var children: [SymbolNode]? + var title: String + var children: [SymbolNode]? - @objc init(_ title: String, _ children: [SymbolNode]? = nil) { - self.title = title - self.children = children - super.init() - } + init(_ title: String, _ children: [SymbolNode]? = nil) { + self.title = title + self.children = children + super.init() + } - @objc init(_ title: String, symbols: String) { - self.title = title - children = Array(symbols).map { SymbolNode(String($0), nil) } - super.init() - } + init(_ title: String, symbols: String) { + self.title = title + children = Array(symbols).map { SymbolNode(String($0), nil) } + super.init() + } - @objc static let catCommonSymbols = String( - format: NSLocalizedString("catCommonSymbols", comment: "")) - @objc static let catHoriBrackets = String( - format: NSLocalizedString("catHoriBrackets", comment: "")) - @objc static let catVertBrackets = String( - format: NSLocalizedString("catVertBrackets", comment: "")) - @objc static let catGreekLetters = String( - format: NSLocalizedString("catGreekLetters", comment: "")) - @objc static let catMathSymbols = String( - format: NSLocalizedString("catMathSymbols", comment: "")) - @objc static let catCurrencyUnits = String( - format: NSLocalizedString("catCurrencyUnits", comment: "")) - @objc static let catSpecialSymbols = String( - format: NSLocalizedString("catSpecialSymbols", comment: "")) - @objc static let catUnicodeSymbols = String( - format: NSLocalizedString("catUnicodeSymbols", comment: "")) - @objc static let catCircledKanjis = String( - format: NSLocalizedString("catCircledKanjis", comment: "")) - @objc static let catCircledKataKana = String( - format: NSLocalizedString("catCircledKataKana", comment: "")) - @objc static let catBracketKanjis = String( - format: NSLocalizedString("catBracketKanjis", comment: "")) - @objc static let catSingleTableLines = String( - format: NSLocalizedString("catSingleTableLines", comment: "")) - @objc static let catDoubleTableLines = String( - format: NSLocalizedString("catDoubleTableLines", comment: "")) - @objc static let catFillingBlocks = String( - format: NSLocalizedString("catFillingBlocks", comment: "")) - @objc static let catLineSegments = String( - format: NSLocalizedString("catLineSegments", comment: "")) + static let catCommonSymbols = String( + format: NSLocalizedString("catCommonSymbols", comment: "")) + static let catHoriBrackets = String( + format: NSLocalizedString("catHoriBrackets", comment: "")) + static let catVertBrackets = String( + format: NSLocalizedString("catVertBrackets", comment: "")) + static let catGreekLetters = String( + format: NSLocalizedString("catGreekLetters", comment: "")) + static let catMathSymbols = String( + format: NSLocalizedString("catMathSymbols", comment: "")) + static let catCurrencyUnits = String( + format: NSLocalizedString("catCurrencyUnits", comment: "")) + static let catSpecialSymbols = String( + format: NSLocalizedString("catSpecialSymbols", comment: "")) + static let catUnicodeSymbols = String( + format: NSLocalizedString("catUnicodeSymbols", comment: "")) + static let catCircledKanjis = String( + format: NSLocalizedString("catCircledKanjis", comment: "")) + static let catCircledKataKana = String( + format: NSLocalizedString("catCircledKataKana", comment: "")) + static let catBracketKanjis = String( + format: NSLocalizedString("catBracketKanjis", comment: "")) + static let catSingleTableLines = String( + format: NSLocalizedString("catSingleTableLines", comment: "")) + static let catDoubleTableLines = String( + format: NSLocalizedString("catDoubleTableLines", comment: "")) + static let catFillingBlocks = String( + format: NSLocalizedString("catFillingBlocks", comment: "")) + static let catLineSegments = String( + format: NSLocalizedString("catLineSegments", comment: "")) - @objc static let root: SymbolNode = .init( - "/", - [ - SymbolNode("`"), - SymbolNode(catCommonSymbols, symbols: ",、。.?!;:‧‥﹐﹒˙·‘’“”〝〞‵′〃~$%@&#*"), - SymbolNode(catHoriBrackets, symbols: "()「」〔〕{}〈〉『』《》【】﹙﹚﹝﹞﹛﹜"), - SymbolNode(catVertBrackets, symbols: "︵︶﹁﹂︹︺︷︸︿﹀﹃﹄︽︾︻︼"), - SymbolNode( - catGreekLetters, symbols: "αβγδεζηθικλμνξοπρστυφχψωΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ" - ), - SymbolNode(catMathSymbols, symbols: "+-×÷=≠≒∞±√<>﹤﹥≦≧∩∪ˇ⊥∠∟⊿㏒㏑∫∮∵∴╳﹢"), - SymbolNode(catCurrencyUnits, symbols: "$€¥¢£₽₨₩฿₺₮₱₭₴₦৲৳૱௹﷼₹₲₪₡₫៛₵₢₸₤₳₥₠₣₰₧₯₶₷"), - SymbolNode(catSpecialSymbols, symbols: "↑↓←→↖↗↙↘↺⇧⇩⇦⇨⇄⇆⇅⇵↻◎○●⊕⊙※△▲☆★◇◆□■▽▼§¥〒¢£♀♂↯"), - SymbolNode(catUnicodeSymbols, symbols: "♨☀☁☂☃♠♥♣♦♩♪♫♬☺☻"), - SymbolNode(catCircledKanjis, symbols: "㊟㊞㊚㊛㊊㊋㊌㊍㊎㊏㊐㊑㊒㊓㊔㊕㊖㊗︎㊘㊙︎㊜㊝㊠㊡㊢㊣㊤㊥㊦㊧㊨㊩㊪㊫㊬㊭㊮㊯㊰🈚︎🈯︎"), - SymbolNode( - catCircledKataKana, symbols: "㋐㋑㋒㋓㋔㋕㋖㋗㋘㋙㋚㋛㋜㋝㋞㋟㋠㋡㋢㋣㋤㋥㋦㋧㋨㋩㋪㋫㋬㋭㋮㋯㋰㋱㋲㋳㋴㋵㋶㋷㋸㋹㋺㋻㋼㋾" - ), - SymbolNode(catBracketKanjis, symbols: "㈪㈫㈬㈭㈮㈯㈰㈱㈲㈳㈴㈵㈶㈷㈸㈹㈺㈻㈼㈽㈾㈿㉀㉁㉂㉃"), - SymbolNode(catSingleTableLines, symbols: "├─┼┴┬┤┌┐╞═╪╡│▕└┘╭╮╰╯"), - SymbolNode(catDoubleTableLines, symbols: "╔╦╗╠═╬╣╓╥╖╒╤╕║╚╩╝╟╫╢╙╨╜╞╪╡╘╧╛"), - SymbolNode(catFillingBlocks, symbols: "_ˍ▁▂▃▄▅▆▇█▏▎▍▌▋▊▉◢◣◥◤"), - SymbolNode(catLineSegments, symbols: "﹣﹦≡|∣∥–︱—︳╴¯ ̄﹉﹊﹍﹎﹋﹌﹏︴∕﹨╱╲/\"), - ] - ) + static let root: SymbolNode = .init( + "/", + [ + SymbolNode("`"), + SymbolNode(catCommonSymbols, symbols: ",、。.?!;:‧‥﹐﹒˙·‘’“”〝〞‵′〃~$%@&#*"), + SymbolNode(catHoriBrackets, symbols: "()「」〔〕{}〈〉『』《》【】﹙﹚﹝﹞﹛﹜"), + SymbolNode(catVertBrackets, symbols: "︵︶﹁﹂︹︺︷︸︿﹀﹃﹄︽︾︻︼"), + SymbolNode( + catGreekLetters, symbols: "αβγδεζηθικλμνξοπρστυφχψωΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ" + ), + SymbolNode(catMathSymbols, symbols: "+-×÷=≠≒∞±√<>﹤﹥≦≧∩∪ˇ⊥∠∟⊿㏒㏑∫∮∵∴╳﹢"), + SymbolNode(catCurrencyUnits, symbols: "$€¥¢£₽₨₩฿₺₮₱₭₴₦৲৳૱௹﷼₹₲₪₡₫៛₵₢₸₤₳₥₠₣₰₧₯₶₷"), + SymbolNode(catSpecialSymbols, symbols: "↑↓←→↖↗↙↘↺⇧⇩⇦⇨⇄⇆⇅⇵↻◎○●⊕⊙※△▲☆★◇◆□■▽▼§¥〒¢£♀♂↯"), + SymbolNode(catUnicodeSymbols, symbols: "♨☀☁☂☃♠♥♣♦♩♪♫♬☺☻"), + SymbolNode(catCircledKanjis, symbols: "㊟㊞㊚㊛㊊㊋㊌㊍㊎㊏㊐㊑㊒㊓㊔㊕㊖㊗︎㊘㊙︎㊜㊝㊠㊡㊢㊣㊤㊥㊦㊧㊨㊩㊪㊫㊬㊭㊮㊯㊰🈚︎🈯︎"), + SymbolNode( + catCircledKataKana, symbols: "㋐㋑㋒㋓㋔㋕㋖㋗㋘㋙㋚㋛㋜㋝㋞㋟㋠㋡㋢㋣㋤㋥㋦㋧㋨㋩㋪㋫㋬㋭㋮㋯㋰㋱㋲㋳㋴㋵㋶㋷㋸㋹㋺㋻㋼㋾" + ), + SymbolNode(catBracketKanjis, symbols: "㈪㈫㈬㈭㈮㈯㈰㈱㈲㈳㈴㈵㈶㈷㈸㈹㈺㈻㈼㈽㈾㈿㉀㉁㉂㉃"), + SymbolNode(catSingleTableLines, symbols: "├─┼┴┬┤┌┐╞═╪╡│▕└┘╭╮╰╯"), + SymbolNode(catDoubleTableLines, symbols: "╔╦╗╠═╬╣╓╥╖╒╤╕║╚╩╝╟╫╢╙╨╜╞╪╡╘╧╛"), + SymbolNode(catFillingBlocks, symbols: "_ˍ▁▂▃▄▅▆▇█▏▎▍▌▋▊▉◢◣◥◤"), + SymbolNode(catLineSegments, symbols: "﹣﹦≡|∣∥–︱—︳╴¯ ̄﹉﹊﹍﹎﹋﹌﹏︴∕﹨╱╲/\"), + ] + ) } diff --git a/Source/Modules/ControllerModules/KeyHandler.h b/Source/Modules/ControllerModules/KeyHandler.h deleted file mode 100644 index 020bad09..00000000 --- a/Source/Modules/ControllerModules/KeyHandler.h +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are of: -// (c) 2021 and onwards The vChewing Project (MIT-NTL License). -/* -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -1. The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -2. No trademark license is granted to use the trade names, trademarks, service -marks, or product names of Contributor, except as required to fulfill notice -requirements above. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -#import - -@class InputHandler; -@class InputState; - -NS_ASSUME_NONNULL_BEGIN - -typedef NSString *const InputMode NS_TYPED_ENUM; -extern InputMode imeModeCHT; -extern InputMode imeModeCHS; -extern InputMode imeModeNULL; - -struct BufferStatePackage -{ - NSString *composedText; - NSInteger cursorIndex; - NSString *resultOfRear; - NSString *resultOfFront; -}; - -@class KeyHandler; - -@protocol KeyHandlerDelegate -- (id)ctlCandidateForKeyHandler:(KeyHandler *)keyHandler; -- (void)keyHandler:(KeyHandler *)keyHandler didSelectCandidateAtIndex:(NSInteger)index ctlCandidate:(id)controller; -- (BOOL)keyHandler:(KeyHandler *)keyHandler didRequestWriteUserPhraseWithState:(InputState *)state; -@end - -@interface KeyHandler : NSObject - -- (BOOL)isBuilderEmpty; - -- (void)fixNodeWithValue:(NSString *)value NS_SWIFT_NAME(fixNode(value:)); -- (void)clear; - -@property(strong, nonatomic) InputMode inputMode; -@property(weak, nonatomic) id delegate; - -// The following items need to be exposed to Swift: -- (void)_walk; -- (NSString *)_popOverflowComposingTextAndWalk; -- (NSArray *)_currentReadings; - -- (BOOL)checkWhetherToneMarkerConfirmsPhoneticReadingBuffer; -- (BOOL)chkKeyValidity:(UniChar)value; -- (BOOL)ifLangModelHasUnigramsForKey:(NSString *)reading; -- (BOOL)isPhoneticReadingBufferEmpty; -- (BOOL)isPrintable:(UniChar)charCode; -- (NSArray *)buildAssociatePhraseArrayWithKey:(NSString *)key; -- (NSArray *)getCandidatesArray; -- (NSInteger)getKeyLengthAtIndexZero; -- (NSInteger)getBuilderCursorIndex; -- (NSInteger)getBuilderLength; -- (NSInteger)getPackagedCursorIndex; -- (NSString *)getComposedText; -- (NSString *)getCompositionFromPhoneticReadingBuffer; -- (NSString *)getStrLocationResult:(BOOL)isFront NS_SWIFT_NAME(getStrLocationResult(isFront:)); -- (NSString *)getSyllableCompositionFromPhoneticReadingBuffer; -- (void)clearPhoneticReadingBuffer; -- (void)combinePhoneticReadingBufferKey:(UniChar)charCode; -- (void)createNewBuilder; -- (void)dealWithOverrideModelSuggestions; -- (void)deleteBuilderReadingAfterCursor; -- (void)deleteBuilderReadingInFrontOfCursor; -- (void)doBackSpaceToPhoneticReadingBuffer; -- (void)ensurePhoneticParser; -- (void)insertReadingToBuilderAtCursor:(NSString *)reading; -- (void)packageBufferStateMaterials; -- (void)removeBuilderAndReset:(BOOL)shouldReset; -- (void)setBuilderCursorIndex:(NSInteger)value; -- (void)setInputModesToLM:(BOOL)isCHS; -- (void)syncBaseLMPrefs; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/Modules/ControllerModules/KeyHandler.mm b/Source/Modules/ControllerModules/KeyHandler.mm deleted file mode 100644 index 256d4168..00000000 --- a/Source/Modules/ControllerModules/KeyHandler.mm +++ /dev/null @@ -1,637 +0,0 @@ -// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are of: -// (c) 2021 and onwards The vChewing Project (MIT-NTL License). -/* -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -1. The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -2. No trademark license is granted to use the trade names, trademarks, service -marks, or product names of Contributor, except as required to fulfill notice -requirements above. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -#import "KeyHandler.h" -#import "Gramambular.h" -#import "LMInstantiator.h" -#import "Mandarin.h" -#import "UserOverrideModel.h" -#import "mgrLangModel_Privates.h" -#import "vChewing-Swift.h" -#import - -InputMode imeModeCHS = ctlInputMethod.kIMEModeCHS; -InputMode imeModeCHT = ctlInputMethod.kIMEModeCHT; -InputMode imeModeNULL = ctlInputMethod.kIMEModeNULL; - -typedef vChewing::LMInstantiator BaseLM; -typedef vChewing::UserOverrideModel UserOverrideLM; -typedef Gramambular::BlockReadingBuilder BlockBuilder; -typedef Mandarin::BopomofoReadingBuffer PhoneticBuffer; - -static const double kEpsilon = 0.000001; - -NSString *packagedComposedText; -NSInteger packagedCursorIndex; -NSString *packagedResultOfRear; -NSString *packagedResultOfFront; - -// NON-SWIFTIFIABLE -static double FindHighestScore(const std::vector &nodes, double epsilon) -{ - double highestScore = 0.0; - for (auto ni = nodes.begin(), ne = nodes.end(); ni != ne; ++ni) - { - double score = ni->node->highestUnigramScore(); - if (score > highestScore) - highestScore = score; - } - return highestScore + epsilon; -} - -// NON-SWIFTIFIABLE -class NodeAnchorDescendingSorter -{ - public: - bool operator()(const Gramambular::NodeAnchor &a, const Gramambular::NodeAnchor &b) const - { - return a.node->key().length() > b.node->key().length(); - } -}; - -// if DEBUG is defined, a DOT file (GraphViz format) will be written to the -// specified path every time the grid is walked -#if DEBUG -static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; -#endif - -// NON-SWIFTIFIABLE -@implementation KeyHandler -{ - // the reading buffer that takes user input - PhoneticBuffer *_bpmfReadingBuffer; - - // language model - BaseLM *_languageModel; - - // user override model - UserOverrideLM *_userOverrideModel; - - // the grid (lattice) builder for the unigrams (and bigrams) - BlockBuilder *_builder; - - // latest walked path (trellis) using the Viterbi algorithm - std::vector _walkedNodes; - - NSString *_inputMode; -} - -@synthesize delegate = _delegate; - -// NON-SWIFTIFIABLE DUE TO VARIABLE AVAILABLE ACCESSIBILITY RANGE. -// VARIABLE: "_inputMode" -- (NSString *)inputMode -{ - return _inputMode; -} - -// NON-SWIFTIFIABLE -- (BOOL)isBuilderEmpty -{ - return (_builder->grid().width() == 0); -} - -// NON-SWIFTIFIABLE DUE TO VARIABLE AVAILABLE ACCESSIBILITY RANGE. -// VARIABLE: "_inputMode" -- (void)setInputMode:(NSString *)value -{ - // 下面這句的「isKindOfClass」是做類型檢查, - // 為了應對出現輸入法 plist 被改壞掉這樣的極端情況。 - BOOL isCHS = [value isKindOfClass:[NSString class]] && [value isEqual:imeModeCHS]; - - // 緊接著將新的簡繁輸入模式提報給 ctlInputMethod: - ctlInputMethod.currentInputMode = isCHS ? imeModeCHS : imeModeCHT; - mgrPrefs.mostRecentInputMode = ctlInputMethod.currentInputMode; - - // 拿當前的 _inputMode 與 ctlInputMethod 的提報結果對比,不同的話則套用新設定: - if (![_inputMode isEqualToString:ctlInputMethod.currentInputMode]) - { - // Reinitiate language models if necessary - [self setInputModesToLM:isCHS]; - - // Synchronize the sub-languageModel state settings to the new LM. - [self syncBaseLMPrefs]; - - [self removeBuilderAndReset:YES]; - - if (![self isPhoneticReadingBufferEmpty]) - [self clearPhoneticReadingBuffer]; - } - _inputMode = ctlInputMethod.currentInputMode; -} - -// NON-SWIFTIFIABLE: Required by an ObjC(pp)-based class. -- (void)dealloc -{ // clean up everything - if (_bpmfReadingBuffer) - delete _bpmfReadingBuffer; - if (_builder) - [self removeBuilderAndReset:NO]; -} - -// NON-SWIFTIFIABLE: Not placeable in swift extensions. -- (instancetype)init -{ - self = [super init]; - if (self) - { - [self ensurePhoneticParser]; - [self setInputMode:ctlInputMethod.currentInputMode]; - } - return self; -} - -// NON-SWIFTIFIABLE -- (void)fixNodeWithValue:(NSString *)value -{ - NSInteger cursorIndex = [self getActualCandidateCursorIndex]; - std::string stringValue(value.UTF8String); - Gramambular::NodeAnchor selectedNode = _builder->grid().fixNodeSelectedCandidate(cursorIndex, stringValue); - if (!mgrPrefs.useSCPCTypingMode) - { // 不要針對逐字選字模式啟用臨時半衰記憶模型。 - // If the length of the readings and the characters do not match, - // it often means it is a special symbol and it should not be stored - // in the user override model. - BOOL addToOverrideModel = YES; - if (selectedNode.spanningLength != [value count]) - addToOverrideModel = NO; - - if (addToOverrideModel) - { - double score = selectedNode.node->scoreForCandidate(stringValue); - if (score <= -12) // 威注音的 SymbolLM 的 Score 是 -12。 - addToOverrideModel = NO; - } - if (addToOverrideModel) - _userOverrideModel->observe(_walkedNodes, cursorIndex, stringValue, [[NSDate date] timeIntervalSince1970]); - } - [self _walk]; - - if (mgrPrefs.moveCursorAfterSelectingCandidate) - { - size_t nextPosition = 0; - for (auto node : _walkedNodes) - { - if (nextPosition >= cursorIndex) - break; - nextPosition += node.spanningLength; - } - if (nextPosition <= [self getBuilderLength]) - [self setBuilderCursorIndex:nextPosition]; - } -} - -// NON-SWIFTIFIABLE -- (void)clear -{ - [self clearPhoneticReadingBuffer]; - _builder->clear(); - _walkedNodes.clear(); -} - -#pragma mark - States Building - -// NON-SWIFTIFIABLE -- (void)packageBufferStateMaterials -{ - // We gather the data through this function, package it, - // and sent it to our Swift extension to build the InputState.Inputting there. - // Otherwise, ObjC++ always bugs for "expecting a type". - - // "updating the composing buffer" means to request the client to "refresh" the text input buffer - // with our "composing text" - NSMutableString *composingBuffer = [[NSMutableString alloc] init]; - NSInteger composedStringCursorIndex = 0; - - // we must do some Unicode codepoint counting to find the actual cursor location for the client - // i.e. we need to take UTF-16 into consideration, for which a surrogate pair takes 2 UniChars - // locations - - size_t readingCursorIndex = 0; - size_t builderCursorIndex = [self getBuilderCursorIndex]; - - NSString *resultOfRear = @""; - NSString *resultOfFront = @""; - - for (std::vector::iterator wi = _walkedNodes.begin(), we = _walkedNodes.end(); wi != we; - ++wi) - { - if ((*wi).node) - { - std::string nodeStr = (*wi).node->currentKeyValue().value; - NSString *valueString = [NSString stringWithUTF8String:nodeStr.c_str()]; - [composingBuffer appendString:valueString]; - - NSArray *splited = [valueString split]; - NSInteger codepointCount = splited.count; - - // this re-aligns the cursor index in the composed string - // (the actual cursor on the screen) with the builder's logical - // cursor (reading) cursor; each built node has a "spanning length" - // (e.g. two reading blocks has a spanning length of 2), and we - // accumulate those lengths to calculate the displayed cursor - // index - size_t spanningLength = (*wi).spanningLength; - if (readingCursorIndex + spanningLength <= builderCursorIndex) - { - composedStringCursorIndex += [valueString length]; - readingCursorIndex += spanningLength; - } - else - { - if (codepointCount == spanningLength) - { - for (size_t i = 0; i < codepointCount && readingCursorIndex < builderCursorIndex; i++) - { - composedStringCursorIndex += [splited[i] length]; - readingCursorIndex++; - } - } - else - { - if (readingCursorIndex < builderCursorIndex) - { - composedStringCursorIndex += [valueString length]; - readingCursorIndex += spanningLength; - if (readingCursorIndex > builderCursorIndex) - { - readingCursorIndex = builderCursorIndex; - } - if (builderCursorIndex == 0) - { - resultOfFront = - [NSString stringWithUTF8String:_builder->readings()[builderCursorIndex].c_str()]; - } - else if (builderCursorIndex >= _builder->readings().size()) - { - resultOfRear = [NSString - stringWithUTF8String:_builder->readings()[_builder->readings().size() - 1].c_str()]; - } - else - { - resultOfFront = - [NSString stringWithUTF8String:_builder->readings()[builderCursorIndex].c_str()]; - resultOfRear = - [NSString stringWithUTF8String:_builder->readings()[builderCursorIndex - 1].c_str()]; - } - } - } - } - } - } - - // now we gather all the info, we separate the composing buffer to two parts, head and tail, - // and insert the reading text (the Mandarin syllable) in between them; - // the reading text is what the user is typing - NSString *head = [composingBuffer substringToIndex:composedStringCursorIndex]; - NSString *reading = [self getCompositionFromPhoneticReadingBuffer]; - NSString *tail = [composingBuffer substringFromIndex:composedStringCursorIndex]; - NSString *composedText = [head stringByAppendingString:[reading stringByAppendingString:tail]]; - NSInteger cursorIndex = composedStringCursorIndex + [reading length]; - - packagedComposedText = composedText; - packagedCursorIndex = cursorIndex; - packagedResultOfRear = resultOfRear; - packagedResultOfFront = resultOfFront; -} - -// NON-SWIFTIFIABLE DUE TO VARIABLE AVAILABLE ACCESSIBILITY RANGE. -- (NSString *)getStrLocationResult:(BOOL)isFront -{ - if (isFront) - return packagedResultOfFront; - else - return packagedResultOfRear; -} - -// NON-SWIFTIFIABLE DUE TO VARIABLE AVAILABLE ACCESSIBILITY RANGE. -- (NSString *)getComposedText -{ - return packagedComposedText; -} - -// NON-SWIFTIFIABLE DUE TO VARIABLE AVAILABLE ACCESSIBILITY RANGE. -- (NSInteger)getPackagedCursorIndex -{ - return packagedCursorIndex; -} - -// NON-SWIFTIFIABLE -- (void)_walk -{ - // retrieve the most likely trellis, i.e. a Maximum Likelihood Estimation - // of the best possible Mandarin characters given the input syllables, - // using the Viterbi algorithm implemented in the Gramambular library - Gramambular::Walker walker(&_builder->grid()); - - // the reverse walk traces the trellis from the end - _walkedNodes = walker.reverseWalk(_builder->grid().width()); - - // then we reverse the nodes so that we get the forward-walked nodes - reverse(_walkedNodes.begin(), _walkedNodes.end()); - - // if DEBUG is defined, a GraphViz file is written to kGraphVizOutputfile -#if DEBUG - std::string dotDump = _builder->grid().dumpDOT(); - NSString *dotStr = [NSString stringWithUTF8String:dotDump.c_str()]; - NSError *error = nil; - - BOOL __unused success = [dotStr writeToFile:kGraphVizOutputfile - atomically:YES - encoding:NSUTF8StringEncoding - error:&error]; -#endif -} - -// NON-SWIFTIFIABLE -- (NSString *)_popOverflowComposingTextAndWalk -{ - // in an ideal world, we can as well let the user type forever, - // but because the Viterbi algorithm has a complexity of O(N^2), - // the walk will become slower as the number of nodes increase, - // therefore we need to auto-commit overflown texts which usually - // lose their influence over the whole MLE anyway -- so that when - // the user type along, the already composed text in the rear side - // of the buffer will be committed (i.e. "popped out"). - - NSString *poppedText = @""; - NSInteger composingBufferSize = mgrPrefs.composingBufferSize; - - if (_builder->grid().width() > (size_t)composingBufferSize) - { - if (_walkedNodes.size() > 0) - { - Gramambular::NodeAnchor &anchor = _walkedNodes[0]; - poppedText = [NSString stringWithUTF8String:anchor.node->currentKeyValue().value.c_str()]; - _builder->removeHeadReadings(anchor.spanningLength); - } - } - - [self _walk]; - return poppedText; -} - -// NON-SWIFTIFIABLE -- (NSArray *)_currentReadings -{ - NSMutableArray *readingsArray = [[NSMutableArray alloc] init]; - std::vector v = _builder->readings(); - for (std::vector::iterator it_i = v.begin(); it_i != v.end(); ++it_i) - [readingsArray addObject:[NSString stringWithUTF8String:it_i->c_str()]]; - return readingsArray; -} - -// NON-SWIFTIFIABLE -- (NSArray *)buildAssociatePhraseArrayWithKey:(NSString *)key -{ - NSMutableArray *array = [NSMutableArray array]; - std::string cppKey = std::string(key.UTF8String); - if (_languageModel->hasAssociatedPhrasesForKey(cppKey)) - { - std::vector phrases = _languageModel->associatedPhrasesForKey(cppKey); - for (auto phrase : phrases) - { - NSString *item = [[NSString alloc] initWithUTF8String:phrase.c_str()]; - [array addObject:item]; - } - } - return array; -} - -#pragma mark - 必須用 ObjCpp 處理的部分: Mandarin - -- (BOOL)chkKeyValidity:(UniChar)charCode -{ - return _bpmfReadingBuffer->isValidKey((char)charCode); -} - -- (BOOL)isPhoneticReadingBufferEmpty -{ - return _bpmfReadingBuffer->isEmpty(); -} - -- (void)clearPhoneticReadingBuffer -{ - _bpmfReadingBuffer->clear(); -} - -- (void)combinePhoneticReadingBufferKey:(UniChar)charCode -{ - _bpmfReadingBuffer->combineKey((char)charCode); -} - -- (BOOL)checkWhetherToneMarkerConfirmsPhoneticReadingBuffer -{ - return _bpmfReadingBuffer->hasToneMarker(); -} - -- (NSString *)getSyllableCompositionFromPhoneticReadingBuffer -{ - return [NSString stringWithUTF8String:_bpmfReadingBuffer->syllable().composedString().c_str()]; -} - -- (void)doBackSpaceToPhoneticReadingBuffer -{ - _bpmfReadingBuffer->backspace(); -} - -- (NSString *)getCompositionFromPhoneticReadingBuffer -{ - return [NSString stringWithUTF8String:_bpmfReadingBuffer->composedString().c_str()]; -} - -- (void)ensurePhoneticParser -{ - if (_bpmfReadingBuffer) - { - switch (mgrPrefs.mandarinParser) - { - case MandarinParserOfStandard: - _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::StandardLayout()); - break; - case MandarinParserOfEten: - _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::ETenLayout()); - break; - case MandarinParserOfHsu: - _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::HsuLayout()); - break; - case MandarinParserOfEen26: - _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::ETen26Layout()); - break; - case MandarinParserOfIBM: - _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::IBMLayout()); - break; - case MandarinParserOfMiTAC: - _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::MiTACLayout()); - break; - case MandarinParserOfFakeSeigyou: - _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::FakeSeigyouLayout()); - break; - case MandarinParserOfHanyuPinyin: - _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::HanyuPinyinLayout()); - break; - default: - _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::StandardLayout()); - mgrPrefs.mandarinParser = MandarinParserOfStandard; - } - } - else - { - _bpmfReadingBuffer = new Mandarin::BopomofoReadingBuffer(Mandarin::BopomofoKeyboardLayout::StandardLayout()); - } -} - -#pragma mark - 必須用 ObjCpp 處理的部分: Gramambular 等 - -- (void)removeBuilderAndReset:(BOOL)shouldReset -{ - if (_builder) - { - delete _builder; - if (shouldReset) - [self createNewBuilder]; - } - else if (shouldReset) - [self createNewBuilder]; -} - -- (void)createNewBuilder -{ - _builder = new Gramambular::BlockReadingBuilder(_languageModel); - // Each Mandarin syllable is separated by a hyphen. - _builder->setJoinSeparator("-"); -} - -- (void)setInputModesToLM:(BOOL)isCHS -{ - _languageModel = isCHS ? [mgrLangModel lmCHS] : [mgrLangModel lmCHT]; - _userOverrideModel = isCHS ? [mgrLangModel userOverrideModelCHS] : [mgrLangModel userOverrideModelCHT]; -} - -- (void)syncBaseLMPrefs -{ - if (_languageModel) - { - _languageModel->setPhraseReplacementEnabled(mgrPrefs.phraseReplacementEnabled); - _languageModel->setSymbolEnabled(mgrPrefs.symbolInputEnabled); - _languageModel->setCNSEnabled(mgrPrefs.cns11643Enabled); - } -} - -// ---- - -- (BOOL)ifLangModelHasUnigramsForKey:(NSString *)reading -{ - return _languageModel->hasUnigramsForKey((std::string)[reading UTF8String]); -} - -- (void)insertReadingToBuilderAtCursor:(NSString *)reading -{ - _builder->insertReadingAtCursor((std::string)[reading UTF8String]); -} - -- (void)dealWithOverrideModelSuggestions -{ - // 這一整段都太 C++ 且只出現一次,就整個端過來了。 - // 拆開封裝的話,只會把問題搞得更麻煩而已。 - std::string overrideValue = (mgrPrefs.useSCPCTypingMode) - ? "" - : _userOverrideModel->suggest(_walkedNodes, [self getBuilderCursorIndex], - [[NSDate date] timeIntervalSince1970]); - - if (!overrideValue.empty()) - { - NSInteger cursorIndex = [self getActualCandidateCursorIndex]; - std::vector nodes = mgrPrefs.setRearCursorMode - ? _builder->grid().nodesCrossingOrEndingAt(cursorIndex) - : _builder->grid().nodesEndingAt(cursorIndex); - double highestScore = FindHighestScore(nodes, kEpsilon); - _builder->grid().overrideNodeScoreForSelectedCandidate(cursorIndex, overrideValue, - static_cast(highestScore)); - } -} - -- (void)setBuilderCursorIndex:(NSInteger)value -{ - _builder->setCursorIndex(value); -} - -- (NSInteger)getBuilderCursorIndex -{ - return _builder->cursorIndex(); -} - -- (NSInteger)getBuilderLength -{ - return _builder->length(); -} - -- (void)deleteBuilderReadingInFrontOfCursor -{ - _builder->deleteReadingBeforeCursor(); -} - -- (void)deleteBuilderReadingAfterCursor -{ - _builder->deleteReadingAfterCursor(); -} - -- (NSArray *)getCandidatesArray -{ - NSMutableArray *candidatesArray = [[NSMutableArray alloc] init]; - - NSInteger cursorIndex = [self getActualCandidateCursorIndex]; - std::vector nodes = mgrPrefs.setRearCursorMode - ? _builder->grid().nodesCrossingOrEndingAt(cursorIndex) - : _builder->grid().nodesEndingAt(cursorIndex); - - // sort the nodes, so that longer nodes (representing longer phrases) are placed at the top of the candidate list - stable_sort(nodes.begin(), nodes.end(), NodeAnchorDescendingSorter()); - - // then use the C++ trick to retrieve the candidates for each node at/crossing the cursor - for (std::vector::iterator ni = nodes.begin(), ne = nodes.end(); ni != ne; ++ni) - { - const std::vector &candidates = (*ni).node->candidates(); - for (std::vector::const_iterator ci = candidates.begin(), ce = candidates.end(); - ci != ce; ++ci) - [candidatesArray addObject:[NSString stringWithUTF8String:(*ci).value.c_str()]]; - } - return candidatesArray; -} - -- (NSInteger)getKeyLengthAtIndexZero -{ - return [NSString stringWithUTF8String:_walkedNodes[0].node->currentKeyValue().value.c_str()].length; -} - -#pragma mark - 威注音認為有必要單獨拿出來處理的部分,交給 Swift 則有些困難。 - -- (BOOL)isPrintable:(UniChar)charCode -{ - return isprint(charCode); -} - -@end diff --git a/Source/Modules/ControllerModules/KeyHandler_Core.swift b/Source/Modules/ControllerModules/KeyHandler_Core.swift new file mode 100644 index 00000000..4508d704 --- /dev/null +++ b/Source/Modules/ControllerModules/KeyHandler_Core.swift @@ -0,0 +1,324 @@ +// Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// Refactored from the ObjCpp-version of this class by: +// (c) 2011 and onwards The OpenVanilla Project (MIT License). +/* +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +import Cocoa + +public enum InputMode: String { + case imeModeCHS = "org.atelierInmu.inputmethod.vChewing.IMECHS" + case imeModeCHT = "org.atelierInmu.inputmethod.vChewing.IMECHT" + case imeModeNULL = "" +} + +// MARK: - Delegate. + +protocol KeyHandlerDelegate: NSObjectProtocol { + func ctlCandidate(for _: KeyHandler) -> Any + func keyHandler( + _: KeyHandler, didSelectCandidateAt index: Int, + ctlCandidate controller: Any + ) + func keyHandler(_ keyHandler: KeyHandler, didRequestWriteUserPhraseWith state: InputState) + -> Bool +} + +// MARK: - Kernel. + +class KeyHandler: NSObject { + let kEpsilon: Double = 0.000001 + var _inputMode: String = "" + var _languageModel: vChewing.LMInstantiator = .init() + var _userOverrideModel: vChewing.LMUserOverride = .init() + var _builder: Megrez.BlockReadingBuilder + var _walkedNodes: [Megrez.NodeAnchor] = [] + + weak var delegate: KeyHandlerDelegate? + + var inputMode: InputMode { + get { + switch _inputMode { + case "org.atelierInmu.inputmethod.vChewing.IMECHS": + return InputMode.imeModeCHS + case "org.atelierInmu.inputmethod.vChewing.IMECHT": + return InputMode.imeModeCHT + default: + return InputMode.imeModeNULL + } + } + set { setInputMode(newValue.rawValue) } + } + + override init() { + _builder = Megrez.BlockReadingBuilder(lm: _languageModel) + super.init() + Composer.ensureParser() + setInputMode(ctlInputMethod.currentInputMode) + } + + func clear() { + Composer.clearBuffer() + _builder.clear() + _walkedNodes.removeAll() + } + + // 這個函數得獨立出來給 ObjC 使用。 + func setInputMode(_ value: String) { + // 下面這句的「isKindOfClass」是做類型檢查, + // 為了應對出現輸入法 plist 被改壞掉這樣的極端情況。 + let isCHS: Bool = (value == InputMode.imeModeCHS.rawValue) + + // 緊接著將新的簡繁輸入模式提報給 ctlInputMethod: + ctlInputMethod.currentInputMode = isCHS ? InputMode.imeModeCHS.rawValue : InputMode.imeModeCHT.rawValue + mgrPrefs.mostRecentInputMode = ctlInputMethod.currentInputMode + + // 拿當前的 _inputMode 與 ctlInputMethod 的提報結果對比,不同的話則套用新設定: + if _inputMode != ctlInputMethod.currentInputMode { + // Reinitiate language models if necessary + setInputModesToLM(isCHS: isCHS) + + // Synchronize the sub-languageModel state settings to the new LM. + syncBaseLMPrefs() + + // Create new grid builder. + createNewBuilder() + + if !Composer.isBufferEmpty() { + Composer.clearBuffer() + } + } + // 直接寫到衛星模組內,省得類型轉換 + _inputMode = ctlInputMethod.currentInputMode + } + + // MARK: - Functions dealing with Megrez. + + func walk() { + // Retrieve the most likely trellis, i.e. a Maximum Likelihood Estimation + // of the best possible Mandarin characters given the input syllables, + // using the Viterbi algorithm implemented in the Gramambular library + let walker = Megrez.Walker(grid: _builder.grid()) + + // the reverse walk traces the trellis from the end + let walked: [Megrez.NodeAnchor] = walker.reverseWalk(at: _builder.grid().width()) + + // then we use ".reversed()" to reverse the nodes so that we get the forward-walked nodes + _walkedNodes.removeAll() + _walkedNodes.append(contentsOf: walked.reversed()) + } + + func popOverflowComposingTextAndWalk() -> String { + // In ideal situations we can allow users to type infinitely in a buffer. + // However, Viberti algorithm has a complexity of O(N^2), the walk will + // become slower as the number of nodes increase. Therefore, we need to + // auto-commit overflown texts which usually lose their influence over + // the whole MLE anyway -- so that when the user type along, the already + // composed text in the rear side of the buffer will be committed out. + // (i.e. popped out.) + + var poppedText = "" + if _builder.grid().width() > mgrPrefs.composingBufferSize { + if _walkedNodes.count > 0 { + let anchor: Megrez.NodeAnchor = _walkedNodes[0] + if let theNode = anchor.node { + poppedText = theNode.currentKeyValue().value + } + _builder.removeHeadReadings(count: anchor.spanningLength) + } + } + walk() + return poppedText + } + + func buildAssociatePhraseArray(withKey key: String) -> [String] { + var arrResult: [String] = [] + if _languageModel.hasAssociatedPhrasesForKey(key) { + arrResult.append(contentsOf: _languageModel.associatedPhrasesForKey(key)) + } + return arrResult + } + + func fixNode(value: String) { + let cursorIndex: Int = getActualCandidateCursorIndex() + let selectedNode: Megrez.NodeAnchor = _builder.grid().fixNodeSelectedCandidate( + location: cursorIndex, value: value + ) + // 不要針對逐字選字模式啟用臨時半衰記憶模型。 + if !mgrPrefs.useSCPCTypingMode { + // If the length of the readings and the characters do not match, + // it often means it is a special symbol and it should not be stored + // in the user override model. + var addToUserOverrideModel = true + if selectedNode.spanningLength != value.count { + addToUserOverrideModel = false + } + if addToUserOverrideModel { + if let theNode = selectedNode.node { + // 威注音的 SymbolLM 的 Score 是 -12。 + if theNode.scoreFor(candidate: value) <= -12 { + addToUserOverrideModel = false + } + } + } + if addToUserOverrideModel { + _userOverrideModel.observe( + walkedNodes: _walkedNodes, cursorIndex: cursorIndex, candidate: value, + timestamp: NSDate().timeIntervalSince1970 + ) + } + } + walk() + + if mgrPrefs.moveCursorAfterSelectingCandidate { + var nextPosition = 0 + for node in _walkedNodes { + if nextPosition >= cursorIndex { break } + nextPosition += node.spanningLength + } + if nextPosition <= getBuilderLength() { + setBuilderCursorIndex(value: nextPosition) + } + } + } + + func getCandidatesArray() -> [String] { + var arrCandidates: [String] = [] + var arrNodes: [Megrez.NodeAnchor] = [] + arrNodes.append(contentsOf: getRawNodes()) + + /// 原理:nodes 這個回饋結果包含一堆子陣列,分別對應不同詞長的候選字。 + /// 這裡先對陣列排序、讓最長候選字的子陣列的優先權最高。 + /// 這個過程不會傷到子陣列內部的排序。 + if !arrNodes.isEmpty { + // sort the nodes, so that longer nodes (representing longer phrases) + // are placed at the top of the candidate list + arrNodes.sort { $0.keyLength > $1.keyLength } + + // then use the Swift trick to retrieve the candidates for each node at/crossing the cursor + for currentNodeAnchor in arrNodes { + if let currentNode = currentNodeAnchor.node { + for currentCandidate in currentNode.candidates() { + arrCandidates.append(currentCandidate.value) + } + } + } + } + return arrCandidates + } + + func dealWithOverrideModelSuggestions() { + let overrideValue = + mgrPrefs.useSCPCTypingMode + ? "" + : _userOverrideModel.suggest( + walkedNodes: _walkedNodes, cursorIndex: getBuilderCursorIndex(), + timestamp: NSDate().timeIntervalSince1970 + ) + + if !overrideValue.isEmpty { + _builder.grid().overrideNodeScoreForSelectedCandidate( + location: getActualCandidateCursorIndex(), + value: overrideValue, + overridingScore: findHighestScore(nodes: getRawNodes(), epsilon: kEpsilon) + ) + } + } + + func findHighestScore(nodes: [Megrez.NodeAnchor], epsilon: Double) -> Double { + var highestScore: Double = 0 + for currentAnchor in nodes { + if let theNode = currentAnchor.node { + let score = theNode.highestUnigramScore() + if score > highestScore { + highestScore = score + } + } + } + return highestScore + epsilon + } + + // MARK: - Extracted methods and functions. + + func isBuilderEmpty() -> Bool { _builder.grid().width() == 0 } + + func getRawNodes() -> [Megrez.NodeAnchor] { + /// 警告:不要對游標前置風格使用 nodesCrossing,否則會導致游標行為與 macOS 內建注音輸入法不一致。 + /// 微軟新注音輸入法的游標後置風格也是不允許 nodeCrossing 的,但目前 Megrez 暫時缺乏對該特性的支援。 + /// 所以暫時只能將威注音的游標後置風格描述成「跟 Windows 版雅虎奇摩注音一致」。 + mgrPrefs.setRearCursorMode + ? _builder.grid().nodesCrossingOrEndingAt(location: getActualCandidateCursorIndex()) + : _builder.grid().nodesEndingAt(location: getActualCandidateCursorIndex()) + } + + func setInputModesToLM(isCHS: Bool) { + _languageModel = isCHS ? mgrLangModel.lmCHS : mgrLangModel.lmCHT + _userOverrideModel = isCHS ? mgrLangModel.uomCHS : mgrLangModel.uomCHT + } + + func syncBaseLMPrefs() { + _languageModel.isPhraseReplacementEnabled = mgrPrefs.phraseReplacementEnabled + _languageModel.isCNSEnabled = mgrPrefs.cns11643Enabled + _languageModel.isSymbolEnabled = mgrPrefs.symbolInputEnabled + } + + func createNewBuilder() { + _builder = Megrez.BlockReadingBuilder(lm: _languageModel) + // Each Mandarin syllable is separated by a hyphen. + _builder.setJoinSeparator(separator: "-") + } + + func currentReadings() -> [String] { _builder.readings() } + + func ifLangModelHasUnigrams(forKey reading: String) -> Bool { + _languageModel.hasUnigramsFor(key: reading) + } + + func insertReadingToBuilderAtCursor(reading: String) { + _builder.insertReadingAtCursor(reading: reading) + } + + func setBuilderCursorIndex(value: Int) { + _builder.setCursorIndex(newIndex: value) + } + + func getBuilderCursorIndex() -> Int { + _builder.cursorIndex() + } + + func getBuilderLength() -> Int { + _builder.length() + } + + func deleteBuilderReadingInFrontOfCursor() { + _builder.deleteReadingBeforeCursor() + } + + func deleteBuilderReadingAfterCursor() { + _builder.deleteReadingAfterCursor() + } + + func getKeyLengthAtIndexZero() -> Int { + _walkedNodes[0].node?.currentKeyValue().value.count ?? 0 + } +} diff --git a/Source/Modules/ControllerModules/KeyHandler_HandleCandidate.swift b/Source/Modules/ControllerModules/KeyHandler_HandleCandidate.swift index 42746492..f858bfef 100644 --- a/Source/Modules/ControllerModules/KeyHandler_HandleCandidate.swift +++ b/Source/Modules/ControllerModules/KeyHandler_HandleCandidate.swift @@ -28,339 +28,339 @@ import Cocoa // MARK: - § Handle Candidate State. -@objc extension KeyHandler { - func handleCandidate( - state: InputState, - input: InputHandler, - stateCallback: @escaping (InputState) -> Void, - errorCallback: @escaping () -> Void - ) -> Bool { - let inputText = input.inputText - let charCode: UniChar = input.charCode - if let ctlCandidateCurrent = delegate!.ctlCandidate(for: self) as? ctlCandidate { - // MARK: Cancel Candidate +extension KeyHandler { + func handleCandidate( + state: InputState, + input: InputHandler, + stateCallback: @escaping (InputState) -> Void, + errorCallback: @escaping () -> Void + ) -> Bool { + let inputText = input.inputText + let charCode: UniChar = input.charCode + if let ctlCandidateCurrent = delegate!.ctlCandidate(for: self) as? ctlCandidate { + // MARK: Cancel Candidate - let cancelCandidateKey = - input.isBackSpace || input.isESC || input.isDelete - || ((input.isCursorBackward || input.isCursorForward) && input.isShiftHold) + let cancelCandidateKey = + input.isBackSpace || input.isESC || input.isDelete + || ((input.isCursorBackward || input.isCursorForward) && input.isShiftHold) - if cancelCandidateKey { - if (state is InputState.AssociatedPhrases) - || mgrPrefs.useSCPCTypingMode - || isBuilderEmpty() - { - // 如果此時發現當前組字緩衝區為真空的情況的話, - // 就將當前的組字緩衝區析構處理、強制重設輸入狀態。 - // 否則,一個本不該出現的真空組字緩衝區會使前後方向鍵與 BackSpace 鍵失靈。 - // 所以這裡需要對 isBuilderEmpty() 做判定。 - clear() - stateCallback(InputState.EmptyIgnoringPreviousState()) - } else { - stateCallback(buildInputtingState()) - } - return true - } + if cancelCandidateKey { + if (state is InputState.AssociatedPhrases) + || mgrPrefs.useSCPCTypingMode + || isBuilderEmpty() + { + // 如果此時發現當前組字緩衝區為真空的情況的話, + // 就將當前的組字緩衝區析構處理、強制重設輸入狀態。 + // 否則,一個本不該出現的真空組字緩衝區會使前後方向鍵與 BackSpace 鍵失靈。 + // 所以這裡需要對 isBuilderEmpty() 做判定。 + clear() + stateCallback(InputState.EmptyIgnoringPreviousState()) + } else { + stateCallback(buildInputtingState()) + } + return true + } - // MARK: Enter + // MARK: Enter - if input.isEnter { - if state is InputState.AssociatedPhrases { - clear() - stateCallback(InputState.EmptyIgnoringPreviousState()) - return true - } - delegate!.keyHandler( - self, - didSelectCandidateAt: Int(ctlCandidateCurrent.selectedCandidateIndex), - ctlCandidate: ctlCandidateCurrent - ) - return true - } + if input.isEnter { + if state is InputState.AssociatedPhrases { + clear() + stateCallback(InputState.EmptyIgnoringPreviousState()) + return true + } + delegate!.keyHandler( + self, + didSelectCandidateAt: Int(ctlCandidateCurrent.selectedCandidateIndex), + ctlCandidate: ctlCandidateCurrent + ) + return true + } - // MARK: Tab + // MARK: Tab - if input.isTab { - let updated: Bool = - mgrPrefs.specifyShiftTabKeyBehavior - ? (input.isShiftHold - ? ctlCandidateCurrent.showPreviousPage() - : ctlCandidateCurrent.showNextPage()) - : (input.isShiftHold - ? ctlCandidateCurrent.highlightPreviousCandidate() - : ctlCandidateCurrent.highlightNextCandidate()) - if !updated { - IME.prtDebugIntel("9B691919") - errorCallback() - } - return true - } + if input.isTab { + let updated: Bool = + mgrPrefs.specifyShiftTabKeyBehavior + ? (input.isShiftHold + ? ctlCandidateCurrent.showPreviousPage() + : ctlCandidateCurrent.showNextPage()) + : (input.isShiftHold + ? ctlCandidateCurrent.highlightPreviousCandidate() + : ctlCandidateCurrent.highlightNextCandidate()) + if !updated { + IME.prtDebugIntel("9B691919") + errorCallback() + } + return true + } - // MARK: Space + // MARK: Space - if input.isSpace { - let updated: Bool = - mgrPrefs.specifyShiftSpaceKeyBehavior - ? (input.isShiftHold - ? ctlCandidateCurrent.highlightNextCandidate() - : ctlCandidateCurrent.showNextPage()) - : (input.isShiftHold - ? ctlCandidateCurrent.showNextPage() - : ctlCandidateCurrent.highlightNextCandidate()) - if !updated { - IME.prtDebugIntel("A11C781F") - errorCallback() - } - return true - } + if input.isSpace { + let updated: Bool = + mgrPrefs.specifyShiftSpaceKeyBehavior + ? (input.isShiftHold + ? ctlCandidateCurrent.highlightNextCandidate() + : ctlCandidateCurrent.showNextPage()) + : (input.isShiftHold + ? ctlCandidateCurrent.showNextPage() + : ctlCandidateCurrent.highlightNextCandidate()) + if !updated { + IME.prtDebugIntel("A11C781F") + errorCallback() + } + return true + } - // MARK: PgDn + // MARK: PgDn - if input.isPageDown || input.emacsKey == vChewingEmacsKey.nextPage { - let updated: Bool = ctlCandidateCurrent.showNextPage() - if !updated { - IME.prtDebugIntel("9B691919") - errorCallback() - } - return true - } + if input.isPageDown || input.emacsKey == vChewingEmacsKey.nextPage { + let updated: Bool = ctlCandidateCurrent.showNextPage() + if !updated { + IME.prtDebugIntel("9B691919") + errorCallback() + } + return true + } - // MARK: PgUp + // MARK: PgUp - if input.isPageUp { - let updated: Bool = ctlCandidateCurrent.showPreviousPage() - if !updated { - IME.prtDebugIntel("9569955D") - errorCallback() - } - return true - } + if input.isPageUp { + let updated: Bool = ctlCandidateCurrent.showPreviousPage() + if !updated { + IME.prtDebugIntel("9569955D") + errorCallback() + } + return true + } - // MARK: Left Arrow + // MARK: Left Arrow - if input.isLeft { - if ctlCandidateCurrent is ctlCandidateHorizontal { - let updated: Bool = ctlCandidateCurrent.highlightPreviousCandidate() - if !updated { - IME.prtDebugIntel("1145148D") - errorCallback() - } - } else { - let updated: Bool = ctlCandidateCurrent.showPreviousPage() - if !updated { - IME.prtDebugIntel("1919810D") - errorCallback() - } - } - return true - } + if input.isLeft { + if ctlCandidateCurrent is ctlCandidateHorizontal { + let updated: Bool = ctlCandidateCurrent.highlightPreviousCandidate() + if !updated { + IME.prtDebugIntel("1145148D") + errorCallback() + } + } else { + let updated: Bool = ctlCandidateCurrent.showPreviousPage() + if !updated { + IME.prtDebugIntel("1919810D") + errorCallback() + } + } + return true + } - // MARK: EmacsKey Backward + // MARK: EmacsKey Backward - if input.emacsKey == vChewingEmacsKey.backward { - let updated: Bool = ctlCandidateCurrent.highlightPreviousCandidate() - if !updated { - IME.prtDebugIntel("9B89308D") - errorCallback() - } - return true - } + if input.emacsKey == vChewingEmacsKey.backward { + let updated: Bool = ctlCandidateCurrent.highlightPreviousCandidate() + if !updated { + IME.prtDebugIntel("9B89308D") + errorCallback() + } + return true + } - // MARK: Right Arrow + // MARK: Right Arrow - if input.isRight { - if ctlCandidateCurrent is ctlCandidateHorizontal { - let updated: Bool = ctlCandidateCurrent.highlightNextCandidate() - if !updated { - IME.prtDebugIntel("9B65138D") - errorCallback() - } - } else { - let updated: Bool = ctlCandidateCurrent.showNextPage() - if !updated { - IME.prtDebugIntel("9244908D") - errorCallback() - } - } - return true - } + if input.isRight { + if ctlCandidateCurrent is ctlCandidateHorizontal { + let updated: Bool = ctlCandidateCurrent.highlightNextCandidate() + if !updated { + IME.prtDebugIntel("9B65138D") + errorCallback() + } + } else { + let updated: Bool = ctlCandidateCurrent.showNextPage() + if !updated { + IME.prtDebugIntel("9244908D") + errorCallback() + } + } + return true + } - // MARK: EmacsKey Forward + // MARK: EmacsKey Forward - if input.emacsKey == vChewingEmacsKey.forward { - let updated: Bool = ctlCandidateCurrent.highlightNextCandidate() - if !updated { - IME.prtDebugIntel("9B2428D") - errorCallback() - } - return true - } + if input.emacsKey == vChewingEmacsKey.forward { + let updated: Bool = ctlCandidateCurrent.highlightNextCandidate() + if !updated { + IME.prtDebugIntel("9B2428D") + errorCallback() + } + return true + } - // MARK: Up Arrow + // MARK: Up Arrow - if input.isUp { - if ctlCandidateCurrent is ctlCandidateHorizontal { - let updated: Bool = ctlCandidateCurrent.showPreviousPage() - if !updated { - IME.prtDebugIntel("9B614524") - errorCallback() - } - } else { - let updated: Bool = ctlCandidateCurrent.highlightPreviousCandidate() - if !updated { - IME.prtDebugIntel("ASD9908D") - errorCallback() - } - } - return true - } + if input.isUp { + if ctlCandidateCurrent is ctlCandidateHorizontal { + let updated: Bool = ctlCandidateCurrent.showPreviousPage() + if !updated { + IME.prtDebugIntel("9B614524") + errorCallback() + } + } else { + let updated: Bool = ctlCandidateCurrent.highlightPreviousCandidate() + if !updated { + IME.prtDebugIntel("ASD9908D") + errorCallback() + } + } + return true + } - // MARK: Down Arrow + // MARK: Down Arrow - if input.isDown { - if ctlCandidateCurrent is ctlCandidateHorizontal { - let updated: Bool = ctlCandidateCurrent.showNextPage() - if !updated { - IME.prtDebugIntel("92B990DD") - errorCallback() - } - } else { - let updated: Bool = ctlCandidateCurrent.highlightNextCandidate() - if !updated { - IME.prtDebugIntel("6B99908D") - errorCallback() - } - } - return true - } + if input.isDown { + if ctlCandidateCurrent is ctlCandidateHorizontal { + let updated: Bool = ctlCandidateCurrent.showNextPage() + if !updated { + IME.prtDebugIntel("92B990DD") + errorCallback() + } + } else { + let updated: Bool = ctlCandidateCurrent.highlightNextCandidate() + if !updated { + IME.prtDebugIntel("6B99908D") + errorCallback() + } + } + return true + } - // MARK: Home Key + // MARK: Home Key - if input.isHome || input.emacsKey == vChewingEmacsKey.home { - if ctlCandidateCurrent.selectedCandidateIndex == 0 { - IME.prtDebugIntel("9B6EDE8D") - errorCallback() - } else { - ctlCandidateCurrent.selectedCandidateIndex = 0 - } + if input.isHome || input.emacsKey == vChewingEmacsKey.home { + if ctlCandidateCurrent.selectedCandidateIndex == 0 { + IME.prtDebugIntel("9B6EDE8D") + errorCallback() + } else { + ctlCandidateCurrent.selectedCandidateIndex = 0 + } - return true - } + return true + } - // MARK: End Key + // MARK: End Key - var candidates: [String]! + var candidates: [String]! - if let state = state as? InputState.ChoosingCandidate { - candidates = state.candidates - } else if let state = state as? InputState.AssociatedPhrases { - candidates = state.candidates - } + if let state = state as? InputState.ChoosingCandidate { + candidates = state.candidates + } else if let state = state as? InputState.AssociatedPhrases { + candidates = state.candidates + } - if candidates.isEmpty { - return false - } else { // 這裡不用「count > 0」,因為該整數變數只要「!isEmpty」那就必定滿足這個條件。 - if input.isEnd || input.emacsKey == vChewingEmacsKey.end { - if ctlCandidateCurrent.selectedCandidateIndex == UInt(candidates.count - 1) { - IME.prtDebugIntel("9B69AAAD") - errorCallback() - } else { - ctlCandidateCurrent.selectedCandidateIndex = UInt(candidates.count - 1) - } - } - } + if candidates.isEmpty { + return false + } else { // 這裡不用「count > 0」,因為該整數變數只要「!isEmpty」那就必定滿足這個條件。 + if input.isEnd || input.emacsKey == vChewingEmacsKey.end { + if ctlCandidateCurrent.selectedCandidateIndex == UInt(candidates.count - 1) { + IME.prtDebugIntel("9B69AAAD") + errorCallback() + } else { + ctlCandidateCurrent.selectedCandidateIndex = UInt(candidates.count - 1) + } + } + } - // MARK: - Associated Phrases + // MARK: - Associated Phrases - if state is InputState.AssociatedPhrases { - if !input.isShiftHold { return false } - } + if state is InputState.AssociatedPhrases { + if !input.isShiftHold { return false } + } - var index: Int = NSNotFound - var match: String! - if state is InputState.AssociatedPhrases { - match = input.inputTextIgnoringModifiers - } else { - match = inputText - } + var index: Int = NSNotFound + var match: String! + if state is InputState.AssociatedPhrases { + match = input.inputTextIgnoringModifiers + } else { + match = inputText + } - var j = 0 - while j < ctlCandidateCurrent.keyLabels.count { - let label: CandidateKeyLabel = ctlCandidateCurrent.keyLabels[j] - if match.compare(label.key, options: .caseInsensitive, range: nil, locale: .current) == .orderedSame { - index = j - break - } - j += 1 - } + var j = 0 + while j < ctlCandidateCurrent.keyLabels.count { + let label: CandidateKeyLabel = ctlCandidateCurrent.keyLabels[j] + if match.compare(label.key, options: .caseInsensitive, range: nil, locale: .current) == .orderedSame { + index = j + break + } + j += 1 + } - if index != NSNotFound { - let candidateIndex: UInt = ctlCandidateCurrent.candidateIndexAtKeyLabelIndex(UInt(index)) - if candidateIndex != UInt.max { - delegate!.keyHandler( - self, didSelectCandidateAt: Int(candidateIndex), ctlCandidate: ctlCandidateCurrent - ) - return true - } - } + if index != NSNotFound { + let candidateIndex: UInt = ctlCandidateCurrent.candidateIndexAtKeyLabelIndex(UInt(index)) + if candidateIndex != UInt.max { + delegate!.keyHandler( + self, didSelectCandidateAt: Int(candidateIndex), ctlCandidate: ctlCandidateCurrent + ) + return true + } + } - if state is InputState.AssociatedPhrases { return false } + if state is InputState.AssociatedPhrases { return false } - // MARK: SCPC Mode Processing + // MARK: SCPC Mode Processing - if mgrPrefs.useSCPCTypingMode { - var punctuationNamePrefix = "" + if mgrPrefs.useSCPCTypingMode { + var punctuationNamePrefix = "" - if input.isOptionHold { - punctuationNamePrefix = "_alt_punctuation_" - } else if input.isControlHold { - punctuationNamePrefix = "_ctrl_punctuation_" - } else if mgrPrefs.halfWidthPunctuationEnabled { - punctuationNamePrefix = "_half_punctuation_" - } else { - punctuationNamePrefix = "_punctuation_" - } + if input.isOptionHold { + punctuationNamePrefix = "_alt_punctuation_" + } else if input.isControlHold { + punctuationNamePrefix = "_ctrl_punctuation_" + } else if mgrPrefs.halfWidthPunctuationEnabled { + punctuationNamePrefix = "_half_punctuation_" + } else { + punctuationNamePrefix = "_punctuation_" + } - let parser = getCurrentMandarinParser() + let parser = getCurrentMandarinParser() - let arrCustomPunctuations: [String] = [ - punctuationNamePrefix, parser, String(format: "%c", CChar(charCode)), - ] - let customPunctuation: String = arrCustomPunctuations.joined(separator: "") + let arrCustomPunctuations: [String] = [ + punctuationNamePrefix, parser, String(format: "%c", CChar(charCode)), + ] + let customPunctuation: String = arrCustomPunctuations.joined(separator: "") - let arrPunctuations: [String] = [punctuationNamePrefix, String(format: "%c", CChar(charCode))] - let punctuation: String = arrPunctuations.joined(separator: "") + let arrPunctuations: [String] = [punctuationNamePrefix, String(format: "%c", CChar(charCode))] + let punctuation: String = arrPunctuations.joined(separator: "") - var shouldAutoSelectCandidate: Bool = - chkKeyValidity(charCode) || ifLangModelHasUnigrams(forKey: customPunctuation) - || ifLangModelHasUnigrams(forKey: punctuation) + var shouldAutoSelectCandidate: Bool = + Composer.chkKeyValidity(charCode) || ifLangModelHasUnigrams(forKey: customPunctuation) + || ifLangModelHasUnigrams(forKey: punctuation) - if !shouldAutoSelectCandidate, input.isUpperCaseASCIILetterKey { - let letter: String! = String(format: "%@%c", "_letter_", CChar(charCode)) - if ifLangModelHasUnigrams(forKey: letter) { shouldAutoSelectCandidate = true } - } + if !shouldAutoSelectCandidate, input.isUpperCaseASCIILetterKey { + let letter: String! = String(format: "%@%c", "_letter_", CChar(charCode)) + if ifLangModelHasUnigrams(forKey: letter) { shouldAutoSelectCandidate = true } + } - if shouldAutoSelectCandidate { - let candidateIndex: UInt = ctlCandidateCurrent.candidateIndexAtKeyLabelIndex(0) - if candidateIndex != UInt.max { - delegate!.keyHandler( - self, - didSelectCandidateAt: Int(candidateIndex), - ctlCandidate: ctlCandidateCurrent - ) - clear() - let empty = InputState.EmptyIgnoringPreviousState() - stateCallback(empty) - return handle( - input: input, state: empty, stateCallback: stateCallback, errorCallback: errorCallback - ) - } - return true - } - } - } // END: "if let ctlCandidateCurrent" + if shouldAutoSelectCandidate { + let candidateIndex: UInt = ctlCandidateCurrent.candidateIndexAtKeyLabelIndex(0) + if candidateIndex != UInt.max { + delegate!.keyHandler( + self, + didSelectCandidateAt: Int(candidateIndex), + ctlCandidate: ctlCandidateCurrent + ) + clear() + let empty = InputState.EmptyIgnoringPreviousState() + stateCallback(empty) + return handle( + input: input, state: empty, stateCallback: stateCallback, errorCallback: errorCallback + ) + } + return true + } + } + } // END: "if let ctlCandidateCurrent" - IME.prtDebugIntel("172A0F81") - errorCallback() - return true - } + IME.prtDebugIntel("172A0F81") + errorCallback() + return true + } } diff --git a/Source/Modules/ControllerModules/KeyHandler_HandleInput.swift b/Source/Modules/ControllerModules/KeyHandler_HandleInput.swift index 39dac754..8d425a49 100644 --- a/Source/Modules/ControllerModules/KeyHandler_HandleInput.swift +++ b/Source/Modules/ControllerModules/KeyHandler_HandleInput.swift @@ -28,405 +28,405 @@ import Cocoa // MARK: - § Handle Input with States. -@objc extension KeyHandler { - func handle( - input: InputHandler, - state: InputState, - stateCallback: @escaping (InputState) -> Void, - errorCallback: @escaping () -> Void - ) -> Bool { - let charCode: UniChar = input.charCode - var state = state // Turn this incoming constant into variable. - let inputText: String = input.inputText ?? "" - - // Ignore the input if its inputText is empty. - // Reason: such inputs may be functional key combinations. - - if inputText.isEmpty { - return false - } - - // Ignore the input if the composing buffer is empty with no reading - // and there is some function key combination. - let isFunctionKey: Bool = - input.isControlHotKey || (input.isCommandHold || input.isOptionHotKey || input.isNumericPad) - if !(state is InputState.NotEmpty) && !(state is InputState.AssociatedPhrases) && isFunctionKey { - return false - } - - // MARK: Caps Lock processing. - - // If Caps Lock is ON, temporarily disable bopomofo. - // Note: Alphanumerical mode processing. - if input.isBackSpace || input.isEnter || input.isAbsorbedArrowKey || input.isExtraChooseCandidateKey - || input.isExtraChooseCandidateKeyReverse || input.isCursorForward || input.isCursorBackward - { - // Do nothing if backspace is pressed -- we ignore the key - } else if input.isCapsLockOn { - // Process all possible combination, we hope. - clear() - stateCallback(InputState.Empty()) - - // When shift is pressed, don't do further processing... - // ...since it outputs capital letter anyway. - if input.isShiftHold { - return false - } - - // If ASCII but not printable, don't use insertText:replacementRange: - // Certain apps don't handle non-ASCII char insertions. - if charCode < 0x80, !isPrintable(charCode) { - return false - } - - // Commit the entire input buffer. - stateCallback(InputState.Committing(poppedText: inputText.lowercased())) - stateCallback(InputState.Empty()) - - return true - } - - // MARK: Numeric Pad Processing. - - if input.isNumericPad { - if !input.isLeft, !input.isRight, !input.isDown, - !input.isUp, !input.isSpace, isPrintable(charCode) - { - clear() - stateCallback(InputState.Empty()) - stateCallback(InputState.Committing(poppedText: inputText.lowercased())) - stateCallback(InputState.Empty()) - return true - } - } - - // MARK: Handle Candidates. - - if state is InputState.ChoosingCandidate { - return handleCandidate( - state: state, input: input, stateCallback: stateCallback, errorCallback: errorCallback - ) - } - - // MARK: Handle Associated Phrases. - - if state is InputState.AssociatedPhrases { - if handleCandidate( - state: state, input: input, stateCallback: stateCallback, errorCallback: errorCallback - ) { - return true - } else { - stateCallback(InputState.Empty()) - } - } - - // MARK: Handle Marking. - - if let marking = state as? InputState.Marking { - if handleMarkingState( - marking, input: input, stateCallback: stateCallback, - errorCallback: errorCallback - ) { - return true - } - state = marking.convertToInputting() - stateCallback(state) - } - - // MARK: Handle BPMF Keys. - - var composeReading = false - let skipPhoneticHandling = input.isReservedKey || input.isControlHold || input.isOptionHold - - // See if Phonetic reading is valid. - if !skipPhoneticHandling && chkKeyValidity(charCode) { - combinePhoneticReadingBufferKey(charCode) - - // If we have a tone marker, we have to insert the reading to the - // builder in other words, if we don't have a tone marker, we just - // update the composing buffer. - composeReading = checkWhetherToneMarkerConfirmsPhoneticReadingBuffer() - if !composeReading { - stateCallback(buildInputtingState()) - return true - } - } - - // See if we have composition if Enter/Space is hit and buffer is not empty. - // We use "|=" conditioning so that the tone marker key is also taken into account. - // However, Swift does not support "|=". - composeReading = composeReading || (!isPhoneticReadingBufferEmpty() && (input.isSpace || input.isEnter)) - if composeReading { - let reading = getSyllableCompositionFromPhoneticReadingBuffer() - - if !ifLangModelHasUnigrams(forKey: reading) { - IME.prtDebugIntel("B49C0979") - errorCallback() - stateCallback(buildInputtingState()) - return true - } - - // ... and insert it into the lattice grid... - insertReadingToBuilder(atCursor: reading) - - // ... then walk the lattice grid... - let poppedText = _popOverflowComposingTextAndWalk() - - // ... get and tweak override model suggestion if possible... - dealWithOverrideModelSuggestions() - - // ... then update the text. - clearPhoneticReadingBuffer() - - let inputting = buildInputtingState() - inputting.poppedText = poppedText - stateCallback(inputting) - - if mgrPrefs.useSCPCTypingMode { - let choosingCandidates: InputState.ChoosingCandidate = buildCandidate( - state: inputting, - useVerticalMode: input.useVerticalMode - ) - if choosingCandidates.candidates.count == 1 { - clear() - let text: String = choosingCandidates.candidates.first ?? "" - stateCallback(InputState.Committing(poppedText: text)) - - if !mgrPrefs.associatedPhrasesEnabled { - stateCallback(InputState.Empty()) - } else { - if let associatedPhrases = - buildAssociatePhraseState( - withKey: text, - useVerticalMode: input.useVerticalMode - ), !associatedPhrases.candidates.isEmpty - { - stateCallback(associatedPhrases) - } else { - stateCallback(InputState.Empty()) - } - } - } else { - stateCallback(choosingCandidates) - } - } - return true - } - - // MARK: Calling candidate window using Space or Down or PageUp / PageDn. - - if let currentState = state as? InputState.NotEmpty { - if isPhoneticReadingBufferEmpty(), - input.isExtraChooseCandidateKey || input.isExtraChooseCandidateKeyReverse || input.isSpace - || input.isPageDown || input.isPageUp || input.isTab - || (input.useVerticalMode && (input.isVerticalModeOnlyChooseCandidateKey)) - { - if input.isSpace { - // If the Space key is NOT set to be a selection key - if input.isShiftHold || !mgrPrefs.chooseCandidateUsingSpace { - if getBuilderCursorIndex() >= getBuilderLength() { - let composingBuffer = currentState.composingBuffer - if (composingBuffer.count) != 0 { - stateCallback(InputState.Committing(poppedText: composingBuffer)) - } - clear() - stateCallback(InputState.Committing(poppedText: " ")) - stateCallback(InputState.Empty()) - } else if ifLangModelHasUnigrams(forKey: " ") { - insertReadingToBuilder(atCursor: " ") - let poppedText = _popOverflowComposingTextAndWalk() - let inputting = buildInputtingState() - inputting.poppedText = poppedText - stateCallback(inputting) - } - return true - } - } - stateCallback(buildCandidate(state: currentState, useVerticalMode: input.useVerticalMode)) - return true - } - } - - // MARK: - - - // MARK: Esc - - if input.isESC { return handleEsc(state: state, stateCallback: stateCallback, errorCallback: errorCallback) } - - // MARK: Cursor backward - - if input.isCursorBackward || input.emacsKey == vChewingEmacsKey.backward { - return handleBackward( - state: state, - input: input, - stateCallback: stateCallback, - errorCallback: errorCallback - ) - } - - // MARK: Cursor forward - - if input.isCursorForward || input.emacsKey == vChewingEmacsKey.forward { - return handleForward( - state: state, input: input, stateCallback: stateCallback, errorCallback: errorCallback - ) - } - - // MARK: Home - - if input.isHome || input.emacsKey == vChewingEmacsKey.home { - return handleHome(state: state, stateCallback: stateCallback, errorCallback: errorCallback) - } - - // MARK: End - - if input.isEnd || input.emacsKey == vChewingEmacsKey.end { - return handleEnd(state: state, stateCallback: stateCallback, errorCallback: errorCallback) - } - - // MARK: Ctrl+PgLf or Shift+PgLf - - if (input.isControlHold || input.isShiftHold) && (input.isOptionHold && input.isLeft) { - return handleHome(state: state, stateCallback: stateCallback, errorCallback: errorCallback) - } - - // MARK: Ctrl+PgRt or Shift+PgRt - - if (input.isControlHold || input.isShiftHold) && (input.isOptionHold && input.isRight) { - return handleEnd(state: state, stateCallback: stateCallback, errorCallback: errorCallback) - } - - // MARK: AbsorbedArrowKey - - if input.isAbsorbedArrowKey || input.isExtraChooseCandidateKey || input.isExtraChooseCandidateKeyReverse { - return handleAbsorbedArrowKey(state: state, stateCallback: stateCallback, errorCallback: errorCallback) - } - - // MARK: Backspace - - if input.isBackSpace { - return handleBackspace(state: state, stateCallback: stateCallback, errorCallback: errorCallback) - } - - // MARK: Delete - - if input.isDelete || input.emacsKey == vChewingEmacsKey.delete { - return handleDelete(state: state, stateCallback: stateCallback, errorCallback: errorCallback) - } - - // MARK: Enter - - if input.isEnter { - return (input.isCommandHold && input.isControlHold) - ? handleCtrlCommandEnter(state: state, stateCallback: stateCallback, errorCallback: errorCallback) - : handleEnter(state: state, stateCallback: stateCallback, errorCallback: errorCallback) - } - - // MARK: - - - // MARK: Punctuation list - - if input.isSymbolMenuPhysicalKey && !input.isShiftHold { - if !input.isOptionHold { - if ifLangModelHasUnigrams(forKey: "_punctuation_list") { - if isPhoneticReadingBufferEmpty() { - insertReadingToBuilder(atCursor: "_punctuation_list") - let poppedText: String! = _popOverflowComposingTextAndWalk() - let inputting = buildInputtingState() - inputting.poppedText = poppedText - stateCallback(inputting) - stateCallback(buildCandidate(state: inputting, useVerticalMode: input.useVerticalMode)) - } else { // If there is still unfinished bpmf reading, ignore the punctuation - IME.prtDebugIntel("17446655") - errorCallback() - } - return true - } - } else { - // 得在這裡先 commit buffer,不然會導致「在摁 ESC 離開符號選單時會重複輸入上一次的組字區的內容」的不當行為。 - // 於是這裡用「模擬一次 Enter 鍵的操作」使其代為執行這個 commit buffer 的動作。 - // 這裡不需要該函數所傳回的 bool 結果,所以用「_ =」解消掉。 - _ = handleEnter(state: state, stateCallback: stateCallback, errorCallback: errorCallback) - stateCallback(InputState.SymbolTable(node: SymbolNode.root, useVerticalMode: input.useVerticalMode)) - return true - } - } - - // MARK: Punctuation - - // if nothing is matched, see if it's a punctuation key for current layout. - - var punctuationNamePrefix = "" - - if input.isOptionHold { - punctuationNamePrefix = "_alt_punctuation_" - } else if input.isControlHold { - punctuationNamePrefix = "_ctrl_punctuation_" - } else if mgrPrefs.halfWidthPunctuationEnabled { - punctuationNamePrefix = "_half_punctuation_" - } else { - punctuationNamePrefix = "_punctuation_" - } - - let parser = getCurrentMandarinParser() - let arrCustomPunctuations: [String] = [ - punctuationNamePrefix, parser, String(format: "%c", CChar(charCode)), - ] - let customPunctuation: String = arrCustomPunctuations.joined(separator: "") - if handlePunctuation( - customPunctuation, - state: state, - usingVerticalMode: input.useVerticalMode, - stateCallback: stateCallback, - errorCallback: errorCallback - ) { - return true - } - - // if nothing is matched, see if it's a punctuation key. - let arrPunctuations: [String] = [punctuationNamePrefix, String(format: "%c", CChar(charCode))] - let punctuation: String = arrPunctuations.joined(separator: "") - - if handlePunctuation( - punctuation, - state: state, - usingVerticalMode: input.useVerticalMode, - stateCallback: stateCallback, - errorCallback: errorCallback - ) { - return true - } - - // 這裡不使用小麥注音 2.2 版的組字區處理方式,而是直接由詞庫負責。 - if input.isUpperCaseASCIILetterKey { - let letter: String! = String(format: "%@%c", "_letter_", CChar(charCode)) - if handlePunctuation( - letter, - state: state, - usingVerticalMode: input.useVerticalMode, - stateCallback: stateCallback, - errorCallback: errorCallback - ) { - return true - } - } - - // MARK: - Still Nothing. - - // Still nothing? Then we update the composing buffer. - // Note that some app has strange behavior if we don't do this, - // "thinking" that the key is not actually consumed. - // 砍掉這一段會導致「F1-F12 按鍵干擾組字區」的問題。 - // 暫時只能先恢復這段,且補上偵錯彙報機制,方便今後排查故障。 - if (state is InputState.NotEmpty) || !isPhoneticReadingBufferEmpty() { - IME.prtDebugIntel( - "Blocked data: charCode: \(charCode), keyCode: \(input.keyCode)") - IME.prtDebugIntel("A9BFF20E") - errorCallback() - stateCallback(state) - return true - } - - return false - } +extension KeyHandler { + func handle( + input: InputHandler, + state: InputState, + stateCallback: @escaping (InputState) -> Void, + errorCallback: @escaping () -> Void + ) -> Bool { + let charCode: UniChar = input.charCode + var state = state // Turn this incoming constant into variable. + let inputText: String = input.inputText ?? "" + + // Ignore the input if its inputText is empty. + // Reason: such inputs may be functional key combinations. + + if inputText.isEmpty { + return false + } + + // Ignore the input if the composing buffer is empty with no reading + // and there is some function key combination. + let isFunctionKey: Bool = + input.isControlHotKey || (input.isCommandHold || input.isOptionHotKey || input.isNumericPad) + if !(state is InputState.NotEmpty) && !(state is InputState.AssociatedPhrases) && isFunctionKey { + return false + } + + // MARK: Caps Lock processing. + + // If Caps Lock is ON, temporarily disable bopomofo. + // Note: Alphanumerical mode processing. + if input.isBackSpace || input.isEnter || input.isAbsorbedArrowKey || input.isExtraChooseCandidateKey + || input.isExtraChooseCandidateKeyReverse || input.isCursorForward || input.isCursorBackward + { + // Do nothing if backspace is pressed -- we ignore the key + } else if input.isCapsLockOn { + // Process all possible combination, we hope. + clear() + stateCallback(InputState.Empty()) + + // When shift is pressed, don't do further processing... + // ...since it outputs capital letter anyway. + if input.isShiftHold { + return false + } + + // If ASCII but not printable, don't use insertText:replacementRange: + // Certain apps don't handle non-ASCII char insertions. + if charCode < 0x80, !CTools.isPrintable(charCode) { + return false + } + + // Commit the entire input buffer. + stateCallback(InputState.Committing(poppedText: inputText.lowercased())) + stateCallback(InputState.Empty()) + + return true + } + + // MARK: Numeric Pad Processing. + + if input.isNumericPad { + if !input.isLeft, !input.isRight, !input.isDown, + !input.isUp, !input.isSpace, CTools.isPrintable(charCode) + { + clear() + stateCallback(InputState.Empty()) + stateCallback(InputState.Committing(poppedText: inputText.lowercased())) + stateCallback(InputState.Empty()) + return true + } + } + + // MARK: Handle Candidates. + + if state is InputState.ChoosingCandidate { + return handleCandidate( + state: state, input: input, stateCallback: stateCallback, errorCallback: errorCallback + ) + } + + // MARK: Handle Associated Phrases. + + if state is InputState.AssociatedPhrases { + if handleCandidate( + state: state, input: input, stateCallback: stateCallback, errorCallback: errorCallback + ) { + return true + } else { + stateCallback(InputState.Empty()) + } + } + + // MARK: Handle Marking. + + if let marking = state as? InputState.Marking { + if handleMarkingState( + marking, input: input, stateCallback: stateCallback, + errorCallback: errorCallback + ) { + return true + } + state = marking.convertToInputting() + stateCallback(state) + } + + // MARK: Handle BPMF Keys. + + var composeReading = false + let skipPhoneticHandling = input.isReservedKey || input.isControlHold || input.isOptionHold + + // See if Phonetic reading is valid. + if !skipPhoneticHandling && Composer.chkKeyValidity(charCode) { + Composer.combineReadingKey(charCode) + + // If we have a tone marker, we have to insert the reading to the + // builder in other words, if we don't have a tone marker, we just + // update the composing buffer. + composeReading = Composer.checkWhetherToneMarkerConfirms() + if !composeReading { + stateCallback(buildInputtingState()) + return true + } + } + + // See if we have composition if Enter/Space is hit and buffer is not empty. + // We use "|=" conditioning so that the tone marker key is also taken into account. + // However, Swift does not support "|=". + composeReading = composeReading || (!Composer.isBufferEmpty() && (input.isSpace || input.isEnter)) + if composeReading { + let reading = Composer.getSyllableComposition() + + if !ifLangModelHasUnigrams(forKey: reading) { + IME.prtDebugIntel("B49C0979:語彙庫內無「\(reading)」的匹配記錄。") + errorCallback() + stateCallback(buildInputtingState()) + return true + } + + // ... and insert it into the lattice grid... + insertReadingToBuilderAtCursor(reading: reading) + + // ... then walk the lattice grid... + let poppedText = popOverflowComposingTextAndWalk() + + // ... get and tweak override model suggestion if possible... + dealWithOverrideModelSuggestions() + + // ... then update the text. + Composer.clearBuffer() + + let inputting = buildInputtingState() + inputting.poppedText = poppedText + stateCallback(inputting) + + if mgrPrefs.useSCPCTypingMode { + let choosingCandidates: InputState.ChoosingCandidate = buildCandidate( + state: inputting, + useVerticalMode: input.useVerticalMode + ) + if choosingCandidates.candidates.count == 1 { + clear() + let text: String = choosingCandidates.candidates.first ?? "" + stateCallback(InputState.Committing(poppedText: text)) + + if !mgrPrefs.associatedPhrasesEnabled { + stateCallback(InputState.Empty()) + } else { + if let associatedPhrases = + buildAssociatePhraseState( + withKey: text, + useVerticalMode: input.useVerticalMode + ), !associatedPhrases.candidates.isEmpty + { + stateCallback(associatedPhrases) + } else { + stateCallback(InputState.Empty()) + } + } + } else { + stateCallback(choosingCandidates) + } + } + return true + } + + // MARK: Calling candidate window using Space or Down or PageUp / PageDn. + + if let currentState = state as? InputState.NotEmpty { + if Composer.isBufferEmpty(), + input.isExtraChooseCandidateKey || input.isExtraChooseCandidateKeyReverse || input.isSpace + || input.isPageDown || input.isPageUp || input.isTab + || (input.useVerticalMode && (input.isVerticalModeOnlyChooseCandidateKey)) + { + if input.isSpace { + // If the Space key is NOT set to be a selection key + if input.isShiftHold || !mgrPrefs.chooseCandidateUsingSpace { + if getBuilderCursorIndex() >= getBuilderLength() { + let composingBuffer = currentState.composingBuffer + if (composingBuffer.count) != 0 { + stateCallback(InputState.Committing(poppedText: composingBuffer)) + } + clear() + stateCallback(InputState.Committing(poppedText: " ")) + stateCallback(InputState.Empty()) + } else if ifLangModelHasUnigrams(forKey: " ") { + insertReadingToBuilderAtCursor(reading: " ") + let poppedText = popOverflowComposingTextAndWalk() + let inputting = buildInputtingState() + inputting.poppedText = poppedText + stateCallback(inputting) + } + return true + } + } + stateCallback(buildCandidate(state: currentState, useVerticalMode: input.useVerticalMode)) + return true + } + } + + // MARK: - + + // MARK: Esc + + if input.isESC { return handleEsc(state: state, stateCallback: stateCallback, errorCallback: errorCallback) } + + // MARK: Cursor backward + + if input.isCursorBackward || input.emacsKey == vChewingEmacsKey.backward { + return handleBackward( + state: state, + input: input, + stateCallback: stateCallback, + errorCallback: errorCallback + ) + } + + // MARK: Cursor forward + + if input.isCursorForward || input.emacsKey == vChewingEmacsKey.forward { + return handleForward( + state: state, input: input, stateCallback: stateCallback, errorCallback: errorCallback + ) + } + + // MARK: Home + + if input.isHome || input.emacsKey == vChewingEmacsKey.home { + return handleHome(state: state, stateCallback: stateCallback, errorCallback: errorCallback) + } + + // MARK: End + + if input.isEnd || input.emacsKey == vChewingEmacsKey.end { + return handleEnd(state: state, stateCallback: stateCallback, errorCallback: errorCallback) + } + + // MARK: Ctrl+PgLf or Shift+PgLf + + if (input.isControlHold || input.isShiftHold) && (input.isOptionHold && input.isLeft) { + return handleHome(state: state, stateCallback: stateCallback, errorCallback: errorCallback) + } + + // MARK: Ctrl+PgRt or Shift+PgRt + + if (input.isControlHold || input.isShiftHold) && (input.isOptionHold && input.isRight) { + return handleEnd(state: state, stateCallback: stateCallback, errorCallback: errorCallback) + } + + // MARK: AbsorbedArrowKey + + if input.isAbsorbedArrowKey || input.isExtraChooseCandidateKey || input.isExtraChooseCandidateKeyReverse { + return handleAbsorbedArrowKey(state: state, stateCallback: stateCallback, errorCallback: errorCallback) + } + + // MARK: Backspace + + if input.isBackSpace { + return handleBackspace(state: state, stateCallback: stateCallback, errorCallback: errorCallback) + } + + // MARK: Delete + + if input.isDelete || input.emacsKey == vChewingEmacsKey.delete { + return handleDelete(state: state, stateCallback: stateCallback, errorCallback: errorCallback) + } + + // MARK: Enter + + if input.isEnter { + return (input.isCommandHold && input.isControlHold) + ? handleCtrlCommandEnter(state: state, stateCallback: stateCallback, errorCallback: errorCallback) + : handleEnter(state: state, stateCallback: stateCallback, errorCallback: errorCallback) + } + + // MARK: - + + // MARK: Punctuation list + + if input.isSymbolMenuPhysicalKey && !input.isShiftHold { + if !input.isOptionHold { + if ifLangModelHasUnigrams(forKey: "_punctuation_list") { + if Composer.isBufferEmpty() { + insertReadingToBuilderAtCursor(reading: "_punctuation_list") + let poppedText: String! = popOverflowComposingTextAndWalk() + let inputting = buildInputtingState() + inputting.poppedText = poppedText + stateCallback(inputting) + stateCallback(buildCandidate(state: inputting, useVerticalMode: input.useVerticalMode)) + } else { // If there is still unfinished bpmf reading, ignore the punctuation + IME.prtDebugIntel("17446655") + errorCallback() + } + return true + } + } else { + // 得在這裡先 commit buffer,不然會導致「在摁 ESC 離開符號選單時會重複輸入上一次的組字區的內容」的不當行為。 + // 於是這裡用「模擬一次 Enter 鍵的操作」使其代為執行這個 commit buffer 的動作。 + // 這裡不需要該函數所傳回的 bool 結果,所以用「_ =」解消掉。 + _ = handleEnter(state: state, stateCallback: stateCallback, errorCallback: errorCallback) + stateCallback(InputState.SymbolTable(node: SymbolNode.root, useVerticalMode: input.useVerticalMode)) + return true + } + } + + // MARK: Punctuation + + // If nothing is matched, see if it's a punctuation key for current layout. + + var punctuationNamePrefix = "" + + if input.isOptionHold { + punctuationNamePrefix = "_alt_punctuation_" + } else if input.isControlHold { + punctuationNamePrefix = "_ctrl_punctuation_" + } else if mgrPrefs.halfWidthPunctuationEnabled { + punctuationNamePrefix = "_half_punctuation_" + } else { + punctuationNamePrefix = "_punctuation_" + } + + let parser = getCurrentMandarinParser() + let arrCustomPunctuations: [String] = [ + punctuationNamePrefix, parser, String(format: "%c", CChar(charCode)), + ] + let customPunctuation: String = arrCustomPunctuations.joined(separator: "") + if handlePunctuation( + customPunctuation, + state: state, + usingVerticalMode: input.useVerticalMode, + stateCallback: stateCallback, + errorCallback: errorCallback + ) { + return true + } + + // if nothing is matched, see if it's a punctuation key. + let arrPunctuations: [String] = [punctuationNamePrefix, String(format: "%c", CChar(charCode))] + let punctuation: String = arrPunctuations.joined(separator: "") + + if handlePunctuation( + punctuation, + state: state, + usingVerticalMode: input.useVerticalMode, + stateCallback: stateCallback, + errorCallback: errorCallback + ) { + return true + } + + // 這裡不使用小麥注音 2.2 版的組字區處理方式,而是直接由詞庫負責。 + if input.isUpperCaseASCIILetterKey { + let letter: String! = String(format: "%@%c", "_letter_", CChar(charCode)) + if handlePunctuation( + letter, + state: state, + usingVerticalMode: input.useVerticalMode, + stateCallback: stateCallback, + errorCallback: errorCallback + ) { + return true + } + } + + // MARK: - Still Nothing. + + // Still nothing? Then we update the composing buffer. + // Note that some app has strange behavior if we don't do this, + // "thinking" that the key is not actually consumed. + // 砍掉這一段會導致「F1-F12 按鍵干擾組字區」的問題。 + // 暫時只能先恢復這段,且補上偵錯彙報機制,方便今後排查故障。 + if (state is InputState.NotEmpty) || !Composer.isBufferEmpty() { + IME.prtDebugIntel( + "Blocked data: charCode: \(charCode), keyCode: \(input.keyCode)") + IME.prtDebugIntel("A9BFF20E") + errorCallback() + stateCallback(state) + return true + } + + return false + } } diff --git a/Source/Modules/ControllerModules/KeyHandler_Misc.swift b/Source/Modules/ControllerModules/KeyHandler_Misc.swift index 5b79ed19..ad15ee8e 100644 --- a/Source/Modules/ControllerModules/KeyHandler_Misc.swift +++ b/Source/Modules/ControllerModules/KeyHandler_Misc.swift @@ -28,27 +28,27 @@ import Cocoa // MARK: - § Misc functions. -@objc extension KeyHandler { - func getCurrentMandarinParser() -> String { - mgrPrefs.mandarinParserName + "_" - } +extension KeyHandler { + func getCurrentMandarinParser() -> String { + mgrPrefs.mandarinParserName + "_" + } - func getActualCandidateCursorIndex() -> Int { - var cursorIndex = getBuilderCursorIndex() - // Windows Yahoo Kimo IME style, phrase is *at the rear of* the cursor. - // (i.e. the cursor is always *before* the phrase.) - // This is different from MS Phonetics IME style ... - // ... since Windows Yahoo Kimo allows "node crossing". - if (mgrPrefs.setRearCursorMode - && (cursorIndex < getBuilderLength())) - || cursorIndex == 0 - { - if cursorIndex == 0 && !mgrPrefs.setRearCursorMode { - cursorIndex += getKeyLengthAtIndexZero() - } else { - cursorIndex += 1 - } - } - return cursorIndex - } + func getActualCandidateCursorIndex() -> Int { + var cursorIndex = getBuilderCursorIndex() + // Windows Yahoo Kimo IME style, phrase is *at the rear of* the cursor. + // (i.e. the cursor is always *before* the phrase.) + // This is different from MS Phonetics IME style ... + // ... since Windows Yahoo Kimo allows "node crossing". + if (mgrPrefs.setRearCursorMode + && (cursorIndex < getBuilderLength())) + || cursorIndex == 0 + { + if cursorIndex == 0, !mgrPrefs.setRearCursorMode { + cursorIndex += getKeyLengthAtIndexZero() + } else { + cursorIndex += 1 + } + } + return cursorIndex + } } diff --git a/Source/Modules/ControllerModules/KeyHandler_States.swift b/Source/Modules/ControllerModules/KeyHandler_States.swift index d531b770..a11cd900 100644 --- a/Source/Modules/ControllerModules/KeyHandler_States.swift +++ b/Source/Modules/ControllerModules/KeyHandler_States.swift @@ -28,525 +28,548 @@ import Cocoa // MARK: - § State managements. -@objc extension KeyHandler { - // MARK: - 構築狀態(State Building) +extension KeyHandler { + // MARK: - 構築狀態(State Building) - func buildInputtingState() -> InputState.Inputting { - // 觸發資料封裝更新,否則下文拿到的資料會是過期的。 - packageBufferStateMaterials() - // 獲取封裝好的資料 - let composedText = getComposedText() - let packagedCursorIndex = UInt(getPackagedCursorIndex()) - let resultOfRear = getStrLocationResult(isFront: false) - let resultOfFront = getStrLocationResult(isFront: true) + func buildInputtingState() -> InputState.Inputting { + // "Updating the composing buffer" means to request the client + // to "refresh" the text input buffer with our "composing text" + var composingBuffer = "" + var composedStringCursorIndex = 0 - // 初期化狀態 - let newState = InputState.Inputting(composingBuffer: composedText, cursorIndex: packagedCursorIndex) + var readingCursorIndex: size_t = 0 + let builderCursorIndex: size_t = getBuilderCursorIndex() - // 組建提示文本 - var tooltip = "" + // We must do some Unicode codepoint counting to find the actual cursor location for the client + // i.e. we need to take UTF-16 into consideration, for which a surrogate pair takes 2 UniChars + // locations. These processes are inherited from the ObjC++ version of this class and might be + // unnecessary in Swift, but this deduction requires further experiments. + for walkedNode in _walkedNodes { + if let theNode = walkedNode.node { + let strNodeValue = theNode.currentKeyValue().value + composingBuffer += strNodeValue - // 如果在用特定的模式的話,則始終顯示對應的提示。 - // TODO: 該功能無法正常運作,暫時註釋掉。 - // if ctlInputMethod.currentKeyHandler.inputMode == InputMode.imeModeCHT { - // if mgrPrefs.chineseConversionEnabled && !mgrPrefs.shiftJISShinjitaiOutputEnabled { - // tooltip = String( - // format: "%@%@%@", NSLocalizedString("Force KangXi Writing", comment: ""), "\n", - // NSLocalizedString("NotificationSwitchON", comment: "")) - // } else if mgrPrefs.shiftJISShinjitaiOutputEnabled { - // tooltip = String( - // format: "%@%@%@", NSLocalizedString("JIS Shinjitai Output", comment: ""), "\n", - // NSLocalizedString("NotificationSwitchON", comment: "")) - // } - // } + let arrSplit: [NSString] = (strNodeValue as NSString).split() + let codepointCount = arrSplit.count - // 備註:因為目前的輸入法已經有了 NSString Emoji 支援,所以這個工具提示可能不會出現了。 - // 姑且留下來用作萬一時的偵錯用途。 - if resultOfRear != "" || resultOfFront != "" { - tooltip = String( - format: NSLocalizedString("Cursor is between \"%@\" and \"%@\".", comment: ""), - resultOfFront, resultOfRear - ) - } + // This re-aligns the cursor index in the composed string + // (the actual cursor on the screen) with the builder's logical + // cursor (reading) cursor; each built node has a "spanning length" + // (e.g. two reading blocks has a spanning length of 2), and we + // accumulate those lengths to calculate the displayed cursor + // index. + let spanningLength: Int = walkedNode.spanningLength + if readingCursorIndex + spanningLength <= builderCursorIndex { + composedStringCursorIndex += (strNodeValue as NSString).length + readingCursorIndex += spanningLength + } else { + if codepointCount == spanningLength { + var i = 0 + while i < codepointCount, readingCursorIndex < builderCursorIndex { + composedStringCursorIndex += arrSplit[i].length + readingCursorIndex += 1 + i += 1 + } + } else { + if readingCursorIndex < builderCursorIndex { + composedStringCursorIndex += (strNodeValue as NSString).length + readingCursorIndex += spanningLength + if readingCursorIndex > builderCursorIndex { + readingCursorIndex = builderCursorIndex + } + } + } + } + } + } + // Now, we gather all the intel, separate the composing buffer to two parts (head and tail), + // and insert the reading text (the Mandarin syllable) in between them. + // The reading text is what the user is typing. - newState.tooltip = tooltip - return newState - } + let head = String((composingBuffer as NSString).substring(to: composedStringCursorIndex)) + let reading = Composer.getComposition() + let tail = String((composingBuffer as NSString).substring(from: composedStringCursorIndex)) + let composedText = head + reading + tail + let cursorIndex = composedStringCursorIndex + reading.count - // MARK: - 用以生成候選詞陣列及狀態 + return InputState.Inputting(composingBuffer: composedText, cursorIndex: UInt(cursorIndex)) + } - func buildCandidate( - state currentState: InputState.NotEmpty, - useVerticalMode: Bool - ) -> InputState.ChoosingCandidate { - InputState.ChoosingCandidate( - composingBuffer: currentState.composingBuffer, - cursorIndex: currentState.cursorIndex, - candidates: getCandidatesArray(), - useVerticalMode: useVerticalMode - ) - } + // MARK: - 用以生成候選詞陣列及狀態 - // MARK: - 用以接收聯想詞陣列且生成狀態 + func buildCandidate( + state currentState: InputState.NotEmpty, + useVerticalMode: Bool + ) -> InputState.ChoosingCandidate { + InputState.ChoosingCandidate( + composingBuffer: currentState.composingBuffer, + cursorIndex: currentState.cursorIndex, + candidates: getCandidatesArray(), + useVerticalMode: useVerticalMode + ) + } - // 這次重寫時,針對「buildAssociatePhraseStateWithKey」這個(用以生成帶有 - // 聯想詞候選清單的結果的狀態回呼的)函數進行了小幅度的重構處理,使其始終 - // 可以從 ObjC 部分的「buildAssociatePhraseArray」函數獲取到一個內容類型 - // 為「String」的標準 Swift 陣列。這樣一來,該聯想詞狀態回呼函數將始終能 - // 夠傳回正確的結果形態、永遠也無法傳回 nil。於是,所有在用到該函數時以 - // 回傳結果類型判斷作為合法性判斷依據的函數,全都將依據改為檢查傳回的陣列 - // 是否為空:如果陣列為空的話,直接回呼一個空狀態。 - func buildAssociatePhraseState( - withKey key: String!, - useVerticalMode: Bool - ) -> InputState.AssociatedPhrases! { - // 上一行必須要用驚嘆號,否則 Xcode 會誤導你砍掉某些實際上必需的語句。 - InputState.AssociatedPhrases( - candidates: buildAssociatePhraseArray(withKey: key), useVerticalMode: useVerticalMode) - } + // MARK: - 用以接收聯想詞陣列且生成狀態 - // MARK: - 用以處理就地新增自訂語彙時的行為 + // 這次重寫時,針對「buildAssociatePhraseStateWithKey」這個(用以生成帶有 + // 聯想詞候選清單的結果的狀態回呼的)函數進行了小幅度的重構處理,使其始終 + // 可以從 ObjC 部分的「buildAssociatePhraseArray」函數獲取到一個內容類型 + // 為「String」的標準 Swift 陣列。這樣一來,該聯想詞狀態回呼函數將始終能 + // 夠傳回正確的結果形態、永遠也無法傳回 nil。於是,所有在用到該函數時以 + // 回傳結果類型判斷作為合法性判斷依據的函數,全都將依據改為檢查傳回的陣列 + // 是否為空:如果陣列為空的話,直接回呼一個空狀態。 + func buildAssociatePhraseState( + withKey key: String!, + useVerticalMode: Bool + ) -> InputState.AssociatedPhrases! { + // 上一行必須要用驚嘆號,否則 Xcode 會誤導你砍掉某些實際上必需的語句。 + InputState.AssociatedPhrases( + candidates: buildAssociatePhraseArray(withKey: key), useVerticalMode: useVerticalMode + ) + } - func handleMarkingState( - _ state: InputState.Marking, - input: InputHandler, - stateCallback: @escaping (InputState) -> Void, - errorCallback: @escaping () -> Void - ) -> Bool { - if input.isESC { - stateCallback(buildInputtingState()) - return true - } + // MARK: - 用以處理就地新增自訂語彙時的行為 - // Enter - if input.isEnter { - if let keyHandlerDelegate = delegate { - if !keyHandlerDelegate.keyHandler(self, didRequestWriteUserPhraseWith: state) { - IME.prtDebugIntel("5B69CC8D") - errorCallback() - return true - } - } - stateCallback(buildInputtingState()) - return true - } + func handleMarkingState( + _ state: InputState.Marking, + input: InputHandler, + stateCallback: @escaping (InputState) -> Void, + errorCallback: @escaping () -> Void + ) -> Bool { + if input.isESC { + stateCallback(buildInputtingState()) + return true + } - // Shift + Left - if input.isCursorBackward || input.emacsKey == vChewingEmacsKey.backward, input.isShiftHold { - var index = state.markerIndex - if index > 0 { - index = UInt((state.composingBuffer as NSString).previousUtf16Position(for: Int(index))) - let marking = InputState.Marking( - composingBuffer: state.composingBuffer, - cursorIndex: state.cursorIndex, - markerIndex: index, - readings: state.readings - ) - marking.tooltipForInputting = state.tooltipForInputting - stateCallback(marking.markedRange.length == 0 ? marking.convertToInputting() : marking) - } else { - IME.prtDebugIntel("1149908D") - errorCallback() - stateCallback(state) - } - return true - } + // Enter + if input.isEnter { + if let keyHandlerDelegate = delegate { + if !keyHandlerDelegate.keyHandler(self, didRequestWriteUserPhraseWith: state) { + IME.prtDebugIntel("5B69CC8D") + errorCallback() + return true + } + } + stateCallback(buildInputtingState()) + return true + } - // Shift + Right - if input.isCursorForward || input.emacsKey == vChewingEmacsKey.forward, input.isShiftHold { - var index = state.markerIndex - // 這裡繼續用 NSString 是為了與 Zonble 之前引入的 NSStringUtils 相容。 - // 不然的話,這行判斷會失敗、引發「9B51408D」錯誤。 - if index < ((state.composingBuffer as NSString).length) { - index = UInt((state.composingBuffer as NSString).nextUtf16Position(for: Int(index))) - let marking = InputState.Marking( - composingBuffer: state.composingBuffer, - cursorIndex: state.cursorIndex, - markerIndex: index, - readings: state.readings - ) - marking.tooltipForInputting = state.tooltipForInputting - stateCallback(marking.markedRange.length == 0 ? marking.convertToInputting() : marking) - } else { - IME.prtDebugIntel("9B51408D") - errorCallback() - stateCallback(state) - } - return true - } - return false - } + // Shift + Left + if input.isCursorBackward || input.emacsKey == vChewingEmacsKey.backward, input.isShiftHold { + var index = state.markerIndex + if index > 0 { + index = UInt((state.composingBuffer as NSString).previousUtf16Position(for: Int(index))) + let marking = InputState.Marking( + composingBuffer: state.composingBuffer, + cursorIndex: state.cursorIndex, + markerIndex: index, + readings: state.readings + ) + marking.tooltipForInputting = state.tooltipForInputting + stateCallback(marking.markedRange.length == 0 ? marking.convertToInputting() : marking) + } else { + IME.prtDebugIntel("1149908D") + errorCallback() + stateCallback(state) + } + return true + } - // MARK: - 標點輸入處理 + // Shift + Right + if input.isCursorForward || input.emacsKey == vChewingEmacsKey.forward, input.isShiftHold { + var index = state.markerIndex + // 這裡繼續用 NSString 是為了與 Zonble 之前引入的 NSStringUtils 相容。 + // 不然的話,這行判斷會失敗、引發「9B51408D」錯誤。 + if index < ((state.composingBuffer as NSString).length) { + index = UInt((state.composingBuffer as NSString).nextUtf16Position(for: Int(index))) + let marking = InputState.Marking( + composingBuffer: state.composingBuffer, + cursorIndex: state.cursorIndex, + markerIndex: index, + readings: state.readings + ) + marking.tooltipForInputting = state.tooltipForInputting + stateCallback(marking.markedRange.length == 0 ? marking.convertToInputting() : marking) + } else { + IME.prtDebugIntel("9B51408D") + errorCallback() + stateCallback(state) + } + return true + } + return false + } - func handlePunctuation( - _ customPunctuation: String, - state: InputState, - usingVerticalMode useVerticalMode: Bool, - stateCallback: @escaping (InputState) -> Void, - errorCallback: @escaping () -> Void - ) -> Bool { - if !ifLangModelHasUnigrams(forKey: customPunctuation) { - return false - } + // MARK: - 標點輸入處理 - if isPhoneticReadingBufferEmpty() { - insertReadingToBuilder(atCursor: customPunctuation) - let poppedText = _popOverflowComposingTextAndWalk() - let inputting = buildInputtingState() - inputting.poppedText = poppedText - stateCallback(inputting) + func handlePunctuation( + _ customPunctuation: String, + state: InputState, + usingVerticalMode useVerticalMode: Bool, + stateCallback: @escaping (InputState) -> Void, + errorCallback: @escaping () -> Void + ) -> Bool { + if !ifLangModelHasUnigrams(forKey: customPunctuation) { + return false + } - if mgrPrefs.useSCPCTypingMode, isPhoneticReadingBufferEmpty() { - let candidateState = buildCandidate( - state: inputting, - useVerticalMode: useVerticalMode - ) - if candidateState.candidates.count == 1 { - clear() - if let strPoppedText: String = candidateState.candidates.first { - stateCallback(InputState.Committing(poppedText: strPoppedText) as InputState.Committing) - stateCallback(InputState.Empty()) - } else { - stateCallback(candidateState) - } - } else { - stateCallback(candidateState) - } - } - return true - } else { - // If there is still unfinished bpmf reading, ignore the punctuation - IME.prtDebugIntel("A9B69908D") - errorCallback() - stateCallback(state) - return true - } - } + if Composer.isBufferEmpty() { + insertReadingToBuilderAtCursor(reading: customPunctuation) + let poppedText = popOverflowComposingTextAndWalk() + let inputting = buildInputtingState() + inputting.poppedText = poppedText + stateCallback(inputting) - // MARK: - Enter 鍵處理 + if mgrPrefs.useSCPCTypingMode, Composer.isBufferEmpty() { + let candidateState = buildCandidate( + state: inputting, + useVerticalMode: useVerticalMode + ) + if candidateState.candidates.count == 1 { + clear() + if let strPoppedText: String = candidateState.candidates.first { + stateCallback(InputState.Committing(poppedText: strPoppedText) as InputState.Committing) + stateCallback(InputState.Empty()) + } else { + stateCallback(candidateState) + } + } else { + stateCallback(candidateState) + } + } + return true + } else { + // If there is still unfinished bpmf reading, ignore the punctuation + IME.prtDebugIntel("A9B69908D") + errorCallback() + stateCallback(state) + return true + } + } - func handleEnter( - state: InputState, - stateCallback: @escaping (InputState) -> Void, - errorCallback _: @escaping () -> Void - ) -> Bool { - if !(state is InputState.Inputting) { - return false - } + // MARK: - Enter 鍵處理 - clear() + func handleEnter( + state: InputState, + stateCallback: @escaping (InputState) -> Void, + errorCallback _: @escaping () -> Void + ) -> Bool { + if !(state is InputState.Inputting) { + return false + } - if let current = state as? InputState.Inputting { - stateCallback(InputState.Committing(poppedText: current.composingBuffer)) - } + clear() - stateCallback(InputState.Empty()) - return true - } + if let current = state as? InputState.Inputting { + stateCallback(InputState.Committing(poppedText: current.composingBuffer)) + } - // MARK: - CMD+Enter 鍵處理 + stateCallback(InputState.Empty()) + return true + } - func handleCtrlCommandEnter( - state: InputState, - stateCallback: @escaping (InputState) -> Void, - errorCallback _: @escaping () -> Void - ) -> Bool { - if !(state is InputState.Inputting) { - return false - } + // MARK: - CMD+Enter 鍵處理 - let readings: [String] = _currentReadings() - let composingBuffer = - (IME.areWeUsingOurOwnPhraseEditor) - ? readings.joined(separator: "-") - : readings.joined(separator: " ") + func handleCtrlCommandEnter( + state: InputState, + stateCallback: @escaping (InputState) -> Void, + errorCallback _: @escaping () -> Void + ) -> Bool { + if !(state is InputState.Inputting) { + return false + } - clear() + let readings: [String] = currentReadings() + let composingBuffer = + (IME.areWeUsingOurOwnPhraseEditor) + ? readings.joined(separator: "-") + : readings.joined(separator: " ") - stateCallback(InputState.Committing(poppedText: composingBuffer)) - stateCallback(InputState.Empty()) - return true - } + clear() - // MARK: - 處理 Backspace (macOS Delete) 按鍵行為 + stateCallback(InputState.Committing(poppedText: composingBuffer)) + stateCallback(InputState.Empty()) + return true + } - func handleBackspace( - state: InputState, - stateCallback: @escaping (InputState) -> Void, - errorCallback: @escaping () -> Void - ) -> Bool { - if !(state is InputState.Inputting) { - return false - } + // MARK: - 處理 Backspace (macOS Delete) 按鍵行為 - if isPhoneticReadingBufferEmpty() { - if getBuilderCursorIndex() >= 0 { - deleteBuilderReadingInFrontOfCursor() - _walk() - } else { - IME.prtDebugIntel("9D69908D") - errorCallback() - stateCallback(state) - return true - } - } else { - doBackSpaceToPhoneticReadingBuffer() - } + func handleBackspace( + state: InputState, + stateCallback: @escaping (InputState) -> Void, + errorCallback: @escaping () -> Void + ) -> Bool { + if !(state is InputState.Inputting) { + return false + } - if isPhoneticReadingBufferEmpty(), getBuilderLength() == 0 { - stateCallback(InputState.EmptyIgnoringPreviousState()) - } else { - stateCallback(buildInputtingState()) - } - return true - } + if Composer.isBufferEmpty() { + if getBuilderCursorIndex() >= 0 { + deleteBuilderReadingInFrontOfCursor() + walk() + } else { + IME.prtDebugIntel("9D69908D") + errorCallback() + stateCallback(state) + return true + } + } else { + Composer.doBackSpaceToBuffer() + } - // MARK: - 處理 PC Delete (macOS Fn+BackSpace) 按鍵行為 + if Composer.isBufferEmpty(), getBuilderLength() == 0 { + stateCallback(InputState.EmptyIgnoringPreviousState()) + } else { + stateCallback(buildInputtingState()) + } + return true + } - func handleDelete( - state: InputState, - stateCallback: @escaping (InputState) -> Void, - errorCallback: @escaping () -> Void - ) -> Bool { - if !(state is InputState.Inputting) { - return false - } + // MARK: - 處理 PC Delete (macOS Fn+BackSpace) 按鍵行為 - if isPhoneticReadingBufferEmpty() { - if getBuilderCursorIndex() != getBuilderLength() { - deleteBuilderReadingAfterCursor() - _walk() - let inputting = buildInputtingState() - // 這裡不用「count > 0」,因為該整數變數只要「!isEmpty」那就必定滿足這個條件。 - if !inputting.composingBuffer.isEmpty { - stateCallback(InputState.EmptyIgnoringPreviousState()) - } else { - stateCallback(inputting) - } - } else { - IME.prtDebugIntel("9B69938D") - errorCallback() - stateCallback(state) - } - } else { - IME.prtDebugIntel("9C69908D") - errorCallback() - stateCallback(state) - } + func handleDelete( + state: InputState, + stateCallback: @escaping (InputState) -> Void, + errorCallback: @escaping () -> Void + ) -> Bool { + if !(state is InputState.Inputting) { + return false + } - return true - } + if Composer.isBufferEmpty() { + if getBuilderCursorIndex() != getBuilderLength() { + deleteBuilderReadingAfterCursor() + walk() + let inputting = buildInputtingState() + // 這裡不用「count > 0」,因為該整數變數只要「!isEmpty」那就必定滿足這個條件。 + if !inputting.composingBuffer.isEmpty { + stateCallback(InputState.EmptyIgnoringPreviousState()) + } else { + stateCallback(inputting) + } + } else { + IME.prtDebugIntel("9B69938D") + errorCallback() + stateCallback(state) + } + } else { + IME.prtDebugIntel("9C69908D") + errorCallback() + stateCallback(state) + } - // MARK: - 處理與當前文字輸入排版前後方向呈 90 度的那兩個方向鍵的按鍵行為 + return true + } - func handleAbsorbedArrowKey( - state: InputState, - stateCallback: @escaping (InputState) -> Void, - errorCallback: @escaping () -> Void - ) -> Bool { - if !(state is InputState.Inputting) { - return false - } - if !isPhoneticReadingBufferEmpty() { - IME.prtDebugIntel("9B6F908D") - errorCallback() - } - stateCallback(state) - return true - } + // MARK: - 處理與當前文字輸入排版前後方向呈 90 度的那兩個方向鍵的按鍵行為 - // MARK: - 處理 Home 鍵行為 + func handleAbsorbedArrowKey( + state: InputState, + stateCallback: @escaping (InputState) -> Void, + errorCallback: @escaping () -> Void + ) -> Bool { + if !(state is InputState.Inputting) { + return false + } + if !Composer.isBufferEmpty() { + IME.prtDebugIntel("9B6F908D") + errorCallback() + } + stateCallback(state) + return true + } - func handleHome( - state: InputState, - stateCallback: @escaping (InputState) -> Void, - errorCallback: @escaping () -> Void - ) -> Bool { - if !(state is InputState.Inputting) { - return false - } + // MARK: - 處理 Home 鍵行為 - if !isPhoneticReadingBufferEmpty() { - IME.prtDebugIntel("ABC44080") - errorCallback() - stateCallback(state) - return true - } + func handleHome( + state: InputState, + stateCallback: @escaping (InputState) -> Void, + errorCallback: @escaping () -> Void + ) -> Bool { + if !(state is InputState.Inputting) { + return false + } - if getBuilderCursorIndex() != 0 { - setBuilderCursorIndex(0) - stateCallback(buildInputtingState()) - } else { - IME.prtDebugIntel("66D97F90") - errorCallback() - stateCallback(state) - } + if !Composer.isBufferEmpty() { + IME.prtDebugIntel("ABC44080") + errorCallback() + stateCallback(state) + return true + } - return true - } + if getBuilderCursorIndex() != 0 { + setBuilderCursorIndex(value: 0) + stateCallback(buildInputtingState()) + } else { + IME.prtDebugIntel("66D97F90") + errorCallback() + stateCallback(state) + } - // MARK: - 處理 End 鍵行為 + return true + } - func handleEnd( - state: InputState, - stateCallback: @escaping (InputState) -> Void, - errorCallback: @escaping () -> Void - ) -> Bool { - if !(state is InputState.Inputting) { - return false - } + // MARK: - 處理 End 鍵行為 - if !isPhoneticReadingBufferEmpty() { - IME.prtDebugIntel("9B69908D") - errorCallback() - stateCallback(state) - return true - } + func handleEnd( + state: InputState, + stateCallback: @escaping (InputState) -> Void, + errorCallback: @escaping () -> Void + ) -> Bool { + if !(state is InputState.Inputting) { + return false + } - if getBuilderCursorIndex() != getBuilderLength() { - setBuilderCursorIndex(getBuilderLength()) - stateCallback(buildInputtingState()) - } else { - IME.prtDebugIntel("9B69908E") - errorCallback() - stateCallback(state) - } + if !Composer.isBufferEmpty() { + IME.prtDebugIntel("9B69908D") + errorCallback() + stateCallback(state) + return true + } - return true - } + if getBuilderCursorIndex() != getBuilderLength() { + setBuilderCursorIndex(value: getBuilderLength()) + stateCallback(buildInputtingState()) + } else { + IME.prtDebugIntel("9B69908E") + errorCallback() + stateCallback(state) + } - // MARK: - 處理 Esc 鍵行為 + return true + } - func handleEsc( - state: InputState, - stateCallback: @escaping (InputState) -> Void, - errorCallback _: @escaping () -> Void - ) -> Bool { - if !(state is InputState.Inputting) { return false } + // MARK: - 處理 Esc 鍵行為 - let escToClearInputBufferEnabled: Bool = mgrPrefs.escToCleanInputBuffer + func handleEsc( + state: InputState, + stateCallback: @escaping (InputState) -> Void, + errorCallback _: @escaping () -> Void + ) -> Bool { + if !(state is InputState.Inputting) { return false } - if escToClearInputBufferEnabled { - // If the option is enabled, we clear everything in the buffer. - // This includes walked nodes and the reading. Note that this convention - // is by default in macOS 10.0-10.5 built-in Panasonic Hanin and later macOS Zhuyin. - // Some Windows users hate this design, hence the option here to disable it. - clear() - stateCallback(InputState.EmptyIgnoringPreviousState()) - } else { - // If reading is not empty, we cancel the reading. - if !isPhoneticReadingBufferEmpty() { - clearPhoneticReadingBuffer() - if getBuilderLength() == 0 { - stateCallback(InputState.Empty()) - } else { - stateCallback(buildInputtingState()) - } - } - } - return true - } + let escToClearInputBufferEnabled: Bool = mgrPrefs.escToCleanInputBuffer - // MARK: - 處理向前方向鍵的行為 + if escToClearInputBufferEnabled { + // If the option is enabled, we clear everything in the buffer. + // This includes walked nodes and the reading. Note that this convention + // is by default in macOS 10.0-10.5 built-in Panasonic Hanin and later macOS Zhuyin. + // Some Windows users hate this design, hence the option here to disable it. + clear() + stateCallback(InputState.EmptyIgnoringPreviousState()) + } else { + // If reading is not empty, we cancel the reading. + if !Composer.isBufferEmpty() { + Composer.clearBuffer() + if getBuilderLength() == 0 { + stateCallback(InputState.Empty()) + } else { + stateCallback(buildInputtingState()) + } + } + } + return true + } - func handleForward( - state: InputState, - input: InputHandler, - stateCallback: @escaping (InputState) -> Void, - errorCallback: @escaping () -> Void - ) -> Bool { - if !(state is InputState.Inputting) { return false } + // MARK: - 處理向前方向鍵的行為 - if !isPhoneticReadingBufferEmpty() { - IME.prtDebugIntel("B3BA5257") - errorCallback() - stateCallback(state) - return true - } + func handleForward( + state: InputState, + input: InputHandler, + stateCallback: @escaping (InputState) -> Void, + errorCallback: @escaping () -> Void + ) -> Bool { + if !(state is InputState.Inputting) { return false } - if let currentState = state as? InputState.Inputting { - if input.isShiftHold { - // Shift + Right - if currentState.cursorIndex < (currentState.composingBuffer as NSString).length { - let nextPosition = (currentState.composingBuffer as NSString).nextUtf16Position( - for: Int(currentState.cursorIndex)) - let marking: InputState.Marking! = InputState.Marking( - composingBuffer: currentState.composingBuffer, - cursorIndex: currentState.cursorIndex, - markerIndex: UInt(nextPosition), - readings: _currentReadings() - ) - marking.tooltipForInputting = currentState.tooltip - stateCallback(marking) - } else { - IME.prtDebugIntel("BB7F6DB9") - errorCallback() - stateCallback(state) - } - } else { - if getBuilderCursorIndex() < getBuilderLength() { - setBuilderCursorIndex(getBuilderCursorIndex() + 1) - stateCallback(buildInputtingState()) - } else { - IME.prtDebugIntel("A96AAD58") - errorCallback() - stateCallback(state) - } - } - } + if !Composer.isBufferEmpty() { + IME.prtDebugIntel("B3BA5257") + errorCallback() + stateCallback(state) + return true + } - return true - } + if let currentState = state as? InputState.Inputting { + if input.isShiftHold { + // Shift + Right + if currentState.cursorIndex < (currentState.composingBuffer as NSString).length { + let nextPosition = (currentState.composingBuffer as NSString).nextUtf16Position( + for: Int(currentState.cursorIndex)) + let marking: InputState.Marking! = InputState.Marking( + composingBuffer: currentState.composingBuffer, + cursorIndex: currentState.cursorIndex, + markerIndex: UInt(nextPosition), + readings: currentReadings() + ) + marking.tooltipForInputting = currentState.tooltip + stateCallback(marking) + } else { + IME.prtDebugIntel("BB7F6DB9") + errorCallback() + stateCallback(state) + } + } else { + if getBuilderCursorIndex() < getBuilderLength() { + setBuilderCursorIndex(value: getBuilderCursorIndex() + 1) + stateCallback(buildInputtingState()) + } else { + IME.prtDebugIntel("A96AAD58") + errorCallback() + stateCallback(state) + } + } + } - // MARK: - 處理向後方向鍵的行為 + return true + } - func handleBackward( - state: InputState, - input: InputHandler, - stateCallback: @escaping (InputState) -> Void, - errorCallback: @escaping () -> Void - ) -> Bool { - if !(state is InputState.Inputting) { return false } + // MARK: - 處理向後方向鍵的行為 - if !isPhoneticReadingBufferEmpty() { - IME.prtDebugIntel("6ED95318") - errorCallback() - stateCallback(state) - return true - } + func handleBackward( + state: InputState, + input: InputHandler, + stateCallback: @escaping (InputState) -> Void, + errorCallback: @escaping () -> Void + ) -> Bool { + if !(state is InputState.Inputting) { return false } - if let currentState = state as? InputState.Inputting { - if input.isShiftHold { - // Shift + left - if currentState.cursorIndex > 0 { - let previousPosition = (currentState.composingBuffer as NSString).previousUtf16Position( - for: Int(currentState.cursorIndex)) - let marking: InputState.Marking! = InputState.Marking( - composingBuffer: currentState.composingBuffer, - cursorIndex: currentState.cursorIndex, - markerIndex: UInt(previousPosition), - readings: _currentReadings() - ) - marking.tooltipForInputting = currentState.tooltip - stateCallback(marking) - } else { - IME.prtDebugIntel("D326DEA3") - errorCallback() - stateCallback(state) - } - } else { - if getBuilderCursorIndex() > 0 { - setBuilderCursorIndex(getBuilderCursorIndex() - 1) - stateCallback(buildInputtingState()) - } else { - IME.prtDebugIntel("7045E6F3") - errorCallback() - stateCallback(state) - } - } - } + if !Composer.isBufferEmpty() { + IME.prtDebugIntel("6ED95318") + errorCallback() + stateCallback(state) + return true + } - return true - } + if let currentState = state as? InputState.Inputting { + if input.isShiftHold { + // Shift + left + if currentState.cursorIndex > 0 { + let previousPosition = (currentState.composingBuffer as NSString).previousUtf16Position( + for: Int(currentState.cursorIndex)) + let marking: InputState.Marking! = InputState.Marking( + composingBuffer: currentState.composingBuffer, + cursorIndex: currentState.cursorIndex, + markerIndex: UInt(previousPosition), + readings: currentReadings() + ) + marking.tooltipForInputting = currentState.tooltip + stateCallback(marking) + } else { + IME.prtDebugIntel("D326DEA3") + errorCallback() + stateCallback(state) + } + } else { + if getBuilderCursorIndex() > 0 { + setBuilderCursorIndex(value: getBuilderCursorIndex() - 1) + stateCallback(buildInputtingState()) + } else { + IME.prtDebugIntel("7045E6F3") + errorCallback() + stateCallback(state) + } + } + } + + return true + } } diff --git a/Source/Modules/ControllerModules/KeyValueBlobReader.cpp b/Source/Modules/ControllerModules/KeyValueBlobReader.cpp deleted file mode 100644 index eee32bbf..00000000 --- a/Source/Modules/ControllerModules/KeyValueBlobReader.cpp +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are of: -// (c) 2021 and onwards The vChewing Project (MIT-NTL License). -/* -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -1. The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -2. No trademark license is granted to use the trade names, trademarks, service -marks, or product names of Contributor, except as required to fulfill notice -requirements above. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -#include "KeyValueBlobReader.h" - -namespace vChewing -{ - -KeyValueBlobReader::State KeyValueBlobReader::Next(KeyValue *out) -{ - static auto new_line = [](char c) { return c == '\n' || c == '\r'; }; - static auto blank = [](char c) { return c == ' ' || c == '\t'; }; - static auto blank_or_newline = [](char c) { return blank(c) || new_line(c); }; - static auto content_char = [](char c) { return !blank(c) && !new_line(c); }; - - if (state_ == State::ERROR) - { - return state_; - } - - const char *key_begin = nullptr; - size_t key_length = 0; - const char *value_begin = nullptr; - size_t value_length = 0; - - while (true) - { - state_ = SkipUntilNot(blank_or_newline); - if (state_ != State::CAN_CONTINUE) - { - return state_; - } - - // Check if it's a comment line; if so, read until end of line. - if (*current_ != '#') - { - break; - } - state_ = SkipUntil(new_line); - if (state_ != State::CAN_CONTINUE) - { - return state_; - } - } - - // No need to check whether* current_ is a content_char, since content_char - // is defined as not blank and not new_line. - - key_begin = current_; - state_ = SkipUntilNot(content_char); - if (state_ != State::CAN_CONTINUE) - { - goto error; - } - key_length = current_ - key_begin; - - // There should be at least one blank character after the key string. - if (!blank(*current_)) - { - goto error; - } - - state_ = SkipUntilNot(blank); - if (state_ != State::CAN_CONTINUE) - { - goto error; - } - - if (!content_char(*current_)) - { - goto error; - } - - value_begin = current_; - // value must only contain content characters, blanks not are allowed. - // also, there's no need to check the state after this, since we will always - // emit the value. This also avoids the situation where trailing spaces in a - // line would become part of the value. - SkipUntilNot(content_char); - value_length = current_ - value_begin; - - // Unconditionally skip until the end of the line. This prevents the case - // like "foo bar baz\n" where baz should not be treated as the Next key. - SkipUntil(new_line); - - if (out != nullptr) - { - *out = KeyValue{std::string_view{key_begin, key_length}, std::string_view{value_begin, value_length}}; - } - state_ = State::HAS_PAIR; - return state_; - -error: - state_ = State::ERROR; - return state_; -} - -KeyValueBlobReader::State KeyValueBlobReader::SkipUntilNot(const std::function &f) -{ - while (current_ != end_ && *current_) - { - if (!f(*current_)) - { - return State::CAN_CONTINUE; - } - ++current_; - } - - return State::END; -} - -KeyValueBlobReader::State KeyValueBlobReader::SkipUntil(const std::function &f) -{ - while (current_ != end_ && *current_) - { - if (f(*current_)) - { - return State::CAN_CONTINUE; - } - ++current_; - } - - return State::END; -} - -std::ostream &operator<<(std::ostream &os, const KeyValueBlobReader::KeyValue &kv) -{ - os << "(key: " << kv.key << ", value: " << kv.value << ")"; - return os; -} - -} // namespace vChewing diff --git a/Source/Modules/ControllerModules/KeyValueBlobReader.h b/Source/Modules/ControllerModules/KeyValueBlobReader.h deleted file mode 100644 index 8ca313be..00000000 --- a/Source/Modules/ControllerModules/KeyValueBlobReader.h +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are of: -// (c) 2021 and onwards The vChewing Project (MIT-NTL License). -/* -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -1. The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -2. No trademark license is granted to use the trade names, trademarks, service -marks, or product names of Contributor, except as required to fulfill notice -requirements above. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -#ifndef SOURCE_ENGINE_KEYVALUEBLOBREADER_H_ -#define SOURCE_ENGINE_KEYVALUEBLOBREADER_H_ - -#include -#include -#include -#include - -// A reader for text-based, blank-separated key-value pairs in a binary blob. -// -// This reader is suitable for reading language model files that entirely -// consist of key-value pairs. Leading or trailing spaces are ignored. -// Lines that start with "#" are treated as comments. Values cannot contain -// spaces. Any space after the value string is parsed is ignored. This implies -// that after a blank, anything that comes after the value can be used as -// comment. Both ' ' and '\t' are treated as blank characters, and the parser -// is agnostic to how lines are ended, and so LF, CR LF, and CR are all valid -// line endings. -// -// std::string_view is used to allow returning results efficiently. As a result, -// the blob is a const char* and will never be mutated. This implies, for -// example, read-only mmap can be used to parse large files. -namespace vChewing -{ - -class KeyValueBlobReader -{ - public: - enum class State : int - { - // There are no more key-value pairs in this blob. - END = 0, - // The reader has produced a new key-value pair. - HAS_PAIR = 1, - // An error is encountered and the parsing stopped. - ERROR = -1, - // Internal-only state: the parser can continue parsing. - CAN_CONTINUE = 2 - }; - - struct KeyValue - { - constexpr KeyValue() : key(""), value("") - { - } - constexpr KeyValue(std::string_view k, std::string_view v) : key(k), value(v) - { - } - - bool operator==(const KeyValue &another) const - { - return key == another.key && value == another.value; - } - - std::string_view key; - std::string_view value; - }; - - KeyValueBlobReader(const char *blob, size_t size) : current_(blob), end_(blob + size) - { - } - - // Parse the next key-value pair and return the state of the reader. If - // `out` is passed, out will be set to the produced key-value pair if there - // is one. - State Next(KeyValue *out = nullptr); - - private: - State SkipUntil(const std::function &f); - State SkipUntilNot(const std::function &f); - - const char *current_; - const char *end_; - State state_ = State::CAN_CONTINUE; -}; - -std::ostream &operator<<(std::ostream &, const KeyValueBlobReader::KeyValue &); - -} // namespace vChewing - -#endif // SOURCE_ENGINE_KEYVALUEBLOBREADER_H_ diff --git a/Source/Modules/ControllerModules/NSStringUtils.swift b/Source/Modules/ControllerModules/NSStringUtils.swift index b2cf0944..f90b0710 100644 --- a/Source/Modules/ControllerModules/NSStringUtils.swift +++ b/Source/Modules/ControllerModules/NSStringUtils.swift @@ -27,49 +27,49 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import Cocoa extension NSString { - /// Converts the index in an NSString to the index in a Swift string. - /// - /// An Emoji might be compose by more than one UTF-16 code points, however - /// the length of an NSString is only the sum of the UTF-16 code points. It - /// causes that the NSString and Swift string representation of the same - /// string have different lengths once the string contains such Emoji. The - /// method helps to find the index in a Swift string by passing the index - /// in an NSString. - public func characterIndex(from utf16Index: Int) -> (Int, String) { - let string = (self as String) - var length = 0 - for (i, character) in string.enumerated() { - length += character.utf16.count - if length > utf16Index { - return (i, string) - } - } - return (string.count, string) - } + /// Converts the index in an NSString to the index in a Swift string. + /// + /// An Emoji might be compose by more than one UTF-16 code points, however + /// the length of an NSString is only the sum of the UTF-16 code points. It + /// causes that the NSString and Swift string representation of the same + /// string have different lengths once the string contains such Emoji. The + /// method helps to find the index in a Swift string by passing the index + /// in an NSString. + public func characterIndex(from utf16Index: Int) -> (Int, String) { + let string = (self as String) + var length = 0 + for (i, character) in string.enumerated() { + length += character.utf16.count + if length > utf16Index { + return (i, string) + } + } + return (string.count, string) + } - @objc public func nextUtf16Position(for index: Int) -> Int { - var (fixedIndex, string) = characterIndex(from: index) - if fixedIndex < string.count { - fixedIndex += 1 - } - return string[.. Int { + var (fixedIndex, string) = characterIndex(from: index) + if fixedIndex < string.count { + fixedIndex += 1 + } + return string[.. Int { - var (fixedIndex, string) = characterIndex(from: index) - if fixedIndex > 0 { - fixedIndex -= 1 - } - return string[.. Int { + var (fixedIndex, string) = characterIndex(from: index) + if fixedIndex > 0 { + fixedIndex -= 1 + } + return string[.. [NSString] { - Array(self as String).map { - NSString(string: String($0)) - } - } + public func split() -> [NSString] { + Array(self as String).map { + NSString(string: String($0)) + } + } } diff --git a/Source/Modules/ControllerModules/vChewingKanjiConverter.swift b/Source/Modules/ControllerModules/vChewingKanjiConverter.swift index 3d84cd9b..c87692a3 100644 --- a/Source/Modules/ControllerModules/vChewingKanjiConverter.swift +++ b/Source/Modules/ControllerModules/vChewingKanjiConverter.swift @@ -25,751 +25,751 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import Cocoa extension String { - fileprivate mutating func selfReplace(_ strOf: String, _ strWith: String = "") { - self = replacingOccurrences(of: strOf, with: strWith) - } + fileprivate mutating func selfReplace(_ strOf: String, _ strWith: String = "") { + self = replacingOccurrences(of: strOf, with: strWith) + } } class vChewingKanjiConverter: NSObject { - @objc class func cnvTradToKangXi(_ strObj: String) -> String { - var strObj = strObj - strObj.selfReplace("偽", "僞") - strObj.selfReplace("啟", "啓") - strObj.selfReplace("吃", "喫") - strObj.selfReplace("嫻", "嫺") - strObj.selfReplace("媯", "嬀") - strObj.selfReplace("峰", "峯") - strObj.selfReplace("么", "幺") - strObj.selfReplace("抬", "擡") - strObj.selfReplace("稜", "棱") - strObj.selfReplace("簷", "檐") - strObj.selfReplace("汙", "污") - strObj.selfReplace("洩", "泄") - strObj.selfReplace("溈", "潙") - strObj.selfReplace("潀", "潨") - strObj.selfReplace("為", "爲") - strObj.selfReplace("床", "牀") - strObj.selfReplace("痺", "痹") - strObj.selfReplace("痴", "癡") - strObj.selfReplace("皂", "皁") - strObj.selfReplace("著", "着") - strObj.selfReplace("睪", "睾") - strObj.selfReplace("秘", "祕") - strObj.selfReplace("灶", "竈") - strObj.selfReplace("粽", "糉") - strObj.selfReplace("韁", "繮") - strObj.selfReplace("才", "纔") - strObj.selfReplace("群", "羣") - strObj.selfReplace("唇", "脣") - strObj.selfReplace("參", "蔘") - strObj.selfReplace("蒍", "蔿") - strObj.selfReplace("眾", "衆") - strObj.selfReplace("裡", "裏") - strObj.selfReplace("核", "覈") - strObj.selfReplace("踴", "踊") - strObj.selfReplace("缽", "鉢") - strObj.selfReplace("針", "鍼") - strObj.selfReplace("鯰", "鮎") - strObj.selfReplace("麵", "麪") - strObj.selfReplace("顎", "齶") - strObj.selfReplace("口喫", "口吃") - strObj.selfReplace("合着", "合著") - strObj.selfReplace("名着", "名著") - strObj.selfReplace("巨着", "巨著") - strObj.selfReplace("鉅着", "鉅著") - strObj.selfReplace("昭着", "昭著") - strObj.selfReplace("所着", "所著") - strObj.selfReplace("遺着", "遺著") - strObj.selfReplace("顯着", "顯著") - strObj.selfReplace("土着", "土著") - strObj.selfReplace("着作", "著作") - strObj.selfReplace("着名", "著名") - strObj.selfReplace("着式", "著式") - strObj.selfReplace("着志", "著志") - strObj.selfReplace("着於", "著於") - strObj.selfReplace("着書", "著書") - strObj.selfReplace("着白", "著白") - strObj.selfReplace("着稱", "著稱") - strObj.selfReplace("着者", "著者") - strObj.selfReplace("着述", "著述") - strObj.selfReplace("着錄", "著錄") - strObj.selfReplace("蹇喫", "蹇吃") - strObj.selfReplace("大着", "大著") - strObj.selfReplace("刊着", "刊著") - strObj.selfReplace("玄着", "玄著") - strObj.selfReplace("白着", "白著") - strObj.selfReplace("住着", "住著") - strObj.selfReplace("刻着", "刻著") - strObj.selfReplace("卓着", "卓著") - strObj.selfReplace("拙着", "拙著") - strObj.selfReplace("查着", "查著") - strObj.selfReplace("炳着", "炳著") - strObj.selfReplace("原着", "原著") - strObj.selfReplace("專着", "專著") - strObj.selfReplace("焯着", "焯著") - strObj.selfReplace("着論", "著論") - strObj.selfReplace("着績", "著績") - strObj.selfReplace("較着", "較著") - strObj.selfReplace("彰着", "彰著") - strObj.selfReplace("撰着", "撰著") - strObj.selfReplace("編着", "編著") - strObj.selfReplace("論着", "論著") - strObj.selfReplace("雜着", "雜著") - strObj.selfReplace("譯着", "譯著") - strObj.selfReplace("地覈", "地核") - strObj.selfReplace("多覈", "多核") - strObj.selfReplace("氘覈", "氘核") - strObj.selfReplace("杏覈", "杏核") - strObj.selfReplace("非覈", "非核") - strObj.selfReplace("覈三", "核三") - strObj.selfReplace("覈下", "核下") - strObj.selfReplace("覈災", "核災") - strObj.selfReplace("覈武", "核武") - strObj.selfReplace("覈狀", "核狀") - strObj.selfReplace("覈桃", "核桃") - strObj.selfReplace("覈彈", "核彈") - strObj.selfReplace("覈戰", "核戰") - strObj.selfReplace("覈糖", "核糖") - strObj.selfReplace("覈醣", "核醣") - strObj.selfReplace("晶覈", "晶核") - strObj.selfReplace("熱覈", "熱核") - strObj.selfReplace("反覈", "反核") - strObj.selfReplace("卵覈", "卵核") - strObj.selfReplace("果覈", "果核") - strObj.selfReplace("剋覈", "剋核") - strObj.selfReplace("覈力", "核力") - strObj.selfReplace("覈子", "核子") - strObj.selfReplace("覈仁", "核仁") - strObj.selfReplace("覈心", "核心") - strObj.selfReplace("覈四", "核四") - strObj.selfReplace("覈果", "核果") - strObj.selfReplace("覈型", "核型") - strObj.selfReplace("覈苷", "核苷") - strObj.selfReplace("覈能", "核能") - strObj.selfReplace("覈傘", "核傘") - strObj.selfReplace("覈發", "核發") - strObj.selfReplace("覈電", "核電") - strObj.selfReplace("覈塵", "核塵") - strObj.selfReplace("覈酸", "核酸") - strObj.selfReplace("覈膜", "核膜") - strObj.selfReplace("覈爆", "核爆") - strObj.selfReplace("痔覈", "痔核") - strObj.selfReplace("陰覈", "陰核") - strObj.selfReplace("殽覈", "殽核") - strObj.selfReplace("結覈", "結核") - strObj.selfReplace("菌覈", "菌核") - strObj.selfReplace("煤覈", "煤核") - strObj.selfReplace("着涎茶", "著涎茶") - strObj.selfReplace("喫口令", "吃口令") - strObj.selfReplace("鄧艾喫", "鄧艾吃") - strObj.selfReplace("杏仁覈", "杏仁核") - strObj.selfReplace("覈一廠", "核一廠") - strObj.selfReplace("覈二廠", "核二廠") - strObj.selfReplace("覈三廠", "核三廠") - strObj.selfReplace("覈融合", "核融合") - strObj.selfReplace("覈四廠", "核四廠") - strObj.selfReplace("覈生化", "核生化") - strObj.selfReplace("覈災變", "核災變") - strObj.selfReplace("覈動力", "核動力") - strObj.selfReplace("覈試爆", "核試爆") - strObj.selfReplace("杏覈兒", "杏核兒") - strObj.selfReplace("原子覈", "原子核") - strObj.selfReplace("覈分裂", "核分裂") - strObj.selfReplace("覈化學", "核化學") - strObj.selfReplace("覈反應", "核反應") - strObj.selfReplace("覈半徑", "核半徑") - strObj.selfReplace("覈污染", "核污染") - strObj.selfReplace("覈武器", "核武器") - strObj.selfReplace("覈苷酸", "核苷酸") - strObj.selfReplace("覈蛋白", "核蛋白") - strObj.selfReplace("覈黃疸", "核黃疸") - strObj.selfReplace("覈黃素", "核黃素") - strObj.selfReplace("覈裝置", "核裝置") - strObj.selfReplace("覈電廠", "核電廠") - strObj.selfReplace("覈廢料", "核廢料") - strObj.selfReplace("覈彈頭", "核彈頭") - strObj.selfReplace("覈潛艇", "核潛艇") - strObj.selfReplace("覈燃料", "核燃料") - strObj.selfReplace("桃覈雕", "桃核雕") - strObj.selfReplace("細胞覈", "細胞核") - strObj.selfReplace("棗覈臉", "棗核臉") - strObj.selfReplace("以微知着", "以微知著") - strObj.selfReplace("見微知着", "見微知著") - strObj.selfReplace("恩威並着", "恩威並著") - strObj.selfReplace("視微知着", "視微知著") - strObj.selfReplace("睹微知着", "睹微知著") - strObj.selfReplace("遐邇着聞", "遐邇著聞") - strObj.selfReplace("積微成着", "積微成著") - strObj.selfReplace("地下覈試", "地下核試") - strObj.selfReplace("地下覈爆", "地下核爆") - strObj.selfReplace("非覈武區", "非核武區") - strObj.selfReplace("覈反應器", "核反應器") - strObj.selfReplace("覈物理學", "核物理學") - strObj.selfReplace("覈能發電", "核能發電") - strObj.selfReplace("覈能電廠", "核能電廠") - strObj.selfReplace("覈能廢料", "核能廢料") - strObj.selfReplace("覈能潛艇", "核能潛艇") - strObj.selfReplace("覈磁共振", "核磁共振") - strObj.selfReplace("熱覈反應", "熱核反應") - strObj.selfReplace("賣李鑽覈", "賣李鑽核") - strObj.selfReplace("雙覈都市", "雙核都市") - strObj.selfReplace("罵人不吐覈", "罵人不吐核") - return strObj - } + class func cnvTradToKangXi(_ strObj: String) -> String { + var strObj = strObj + strObj.selfReplace("偽", "僞") + strObj.selfReplace("啟", "啓") + strObj.selfReplace("吃", "喫") + strObj.selfReplace("嫻", "嫺") + strObj.selfReplace("媯", "嬀") + strObj.selfReplace("峰", "峯") + strObj.selfReplace("么", "幺") + strObj.selfReplace("抬", "擡") + strObj.selfReplace("稜", "棱") + strObj.selfReplace("簷", "檐") + strObj.selfReplace("汙", "污") + strObj.selfReplace("洩", "泄") + strObj.selfReplace("溈", "潙") + strObj.selfReplace("潀", "潨") + strObj.selfReplace("為", "爲") + strObj.selfReplace("床", "牀") + strObj.selfReplace("痺", "痹") + strObj.selfReplace("痴", "癡") + strObj.selfReplace("皂", "皁") + strObj.selfReplace("著", "着") + strObj.selfReplace("睪", "睾") + strObj.selfReplace("秘", "祕") + strObj.selfReplace("灶", "竈") + strObj.selfReplace("粽", "糉") + strObj.selfReplace("韁", "繮") + strObj.selfReplace("才", "纔") + strObj.selfReplace("群", "羣") + strObj.selfReplace("唇", "脣") + strObj.selfReplace("參", "蔘") + strObj.selfReplace("蒍", "蔿") + strObj.selfReplace("眾", "衆") + strObj.selfReplace("裡", "裏") + strObj.selfReplace("核", "覈") + strObj.selfReplace("踴", "踊") + strObj.selfReplace("缽", "鉢") + strObj.selfReplace("針", "鍼") + strObj.selfReplace("鯰", "鮎") + strObj.selfReplace("麵", "麪") + strObj.selfReplace("顎", "齶") + strObj.selfReplace("口喫", "口吃") + strObj.selfReplace("合着", "合著") + strObj.selfReplace("名着", "名著") + strObj.selfReplace("巨着", "巨著") + strObj.selfReplace("鉅着", "鉅著") + strObj.selfReplace("昭着", "昭著") + strObj.selfReplace("所着", "所著") + strObj.selfReplace("遺着", "遺著") + strObj.selfReplace("顯着", "顯著") + strObj.selfReplace("土着", "土著") + strObj.selfReplace("着作", "著作") + strObj.selfReplace("着名", "著名") + strObj.selfReplace("着式", "著式") + strObj.selfReplace("着志", "著志") + strObj.selfReplace("着於", "著於") + strObj.selfReplace("着書", "著書") + strObj.selfReplace("着白", "著白") + strObj.selfReplace("着稱", "著稱") + strObj.selfReplace("着者", "著者") + strObj.selfReplace("着述", "著述") + strObj.selfReplace("着錄", "著錄") + strObj.selfReplace("蹇喫", "蹇吃") + strObj.selfReplace("大着", "大著") + strObj.selfReplace("刊着", "刊著") + strObj.selfReplace("玄着", "玄著") + strObj.selfReplace("白着", "白著") + strObj.selfReplace("住着", "住著") + strObj.selfReplace("刻着", "刻著") + strObj.selfReplace("卓着", "卓著") + strObj.selfReplace("拙着", "拙著") + strObj.selfReplace("查着", "查著") + strObj.selfReplace("炳着", "炳著") + strObj.selfReplace("原着", "原著") + strObj.selfReplace("專着", "專著") + strObj.selfReplace("焯着", "焯著") + strObj.selfReplace("着論", "著論") + strObj.selfReplace("着績", "著績") + strObj.selfReplace("較着", "較著") + strObj.selfReplace("彰着", "彰著") + strObj.selfReplace("撰着", "撰著") + strObj.selfReplace("編着", "編著") + strObj.selfReplace("論着", "論著") + strObj.selfReplace("雜着", "雜著") + strObj.selfReplace("譯着", "譯著") + strObj.selfReplace("地覈", "地核") + strObj.selfReplace("多覈", "多核") + strObj.selfReplace("氘覈", "氘核") + strObj.selfReplace("杏覈", "杏核") + strObj.selfReplace("非覈", "非核") + strObj.selfReplace("覈三", "核三") + strObj.selfReplace("覈下", "核下") + strObj.selfReplace("覈災", "核災") + strObj.selfReplace("覈武", "核武") + strObj.selfReplace("覈狀", "核狀") + strObj.selfReplace("覈桃", "核桃") + strObj.selfReplace("覈彈", "核彈") + strObj.selfReplace("覈戰", "核戰") + strObj.selfReplace("覈糖", "核糖") + strObj.selfReplace("覈醣", "核醣") + strObj.selfReplace("晶覈", "晶核") + strObj.selfReplace("熱覈", "熱核") + strObj.selfReplace("反覈", "反核") + strObj.selfReplace("卵覈", "卵核") + strObj.selfReplace("果覈", "果核") + strObj.selfReplace("剋覈", "剋核") + strObj.selfReplace("覈力", "核力") + strObj.selfReplace("覈子", "核子") + strObj.selfReplace("覈仁", "核仁") + strObj.selfReplace("覈心", "核心") + strObj.selfReplace("覈四", "核四") + strObj.selfReplace("覈果", "核果") + strObj.selfReplace("覈型", "核型") + strObj.selfReplace("覈苷", "核苷") + strObj.selfReplace("覈能", "核能") + strObj.selfReplace("覈傘", "核傘") + strObj.selfReplace("覈發", "核發") + strObj.selfReplace("覈電", "核電") + strObj.selfReplace("覈塵", "核塵") + strObj.selfReplace("覈酸", "核酸") + strObj.selfReplace("覈膜", "核膜") + strObj.selfReplace("覈爆", "核爆") + strObj.selfReplace("痔覈", "痔核") + strObj.selfReplace("陰覈", "陰核") + strObj.selfReplace("殽覈", "殽核") + strObj.selfReplace("結覈", "結核") + strObj.selfReplace("菌覈", "菌核") + strObj.selfReplace("煤覈", "煤核") + strObj.selfReplace("着涎茶", "著涎茶") + strObj.selfReplace("喫口令", "吃口令") + strObj.selfReplace("鄧艾喫", "鄧艾吃") + strObj.selfReplace("杏仁覈", "杏仁核") + strObj.selfReplace("覈一廠", "核一廠") + strObj.selfReplace("覈二廠", "核二廠") + strObj.selfReplace("覈三廠", "核三廠") + strObj.selfReplace("覈融合", "核融合") + strObj.selfReplace("覈四廠", "核四廠") + strObj.selfReplace("覈生化", "核生化") + strObj.selfReplace("覈災變", "核災變") + strObj.selfReplace("覈動力", "核動力") + strObj.selfReplace("覈試爆", "核試爆") + strObj.selfReplace("杏覈兒", "杏核兒") + strObj.selfReplace("原子覈", "原子核") + strObj.selfReplace("覈分裂", "核分裂") + strObj.selfReplace("覈化學", "核化學") + strObj.selfReplace("覈反應", "核反應") + strObj.selfReplace("覈半徑", "核半徑") + strObj.selfReplace("覈污染", "核污染") + strObj.selfReplace("覈武器", "核武器") + strObj.selfReplace("覈苷酸", "核苷酸") + strObj.selfReplace("覈蛋白", "核蛋白") + strObj.selfReplace("覈黃疸", "核黃疸") + strObj.selfReplace("覈黃素", "核黃素") + strObj.selfReplace("覈裝置", "核裝置") + strObj.selfReplace("覈電廠", "核電廠") + strObj.selfReplace("覈廢料", "核廢料") + strObj.selfReplace("覈彈頭", "核彈頭") + strObj.selfReplace("覈潛艇", "核潛艇") + strObj.selfReplace("覈燃料", "核燃料") + strObj.selfReplace("桃覈雕", "桃核雕") + strObj.selfReplace("細胞覈", "細胞核") + strObj.selfReplace("棗覈臉", "棗核臉") + strObj.selfReplace("以微知着", "以微知著") + strObj.selfReplace("見微知着", "見微知著") + strObj.selfReplace("恩威並着", "恩威並著") + strObj.selfReplace("視微知着", "視微知著") + strObj.selfReplace("睹微知着", "睹微知著") + strObj.selfReplace("遐邇着聞", "遐邇著聞") + strObj.selfReplace("積微成着", "積微成著") + strObj.selfReplace("地下覈試", "地下核試") + strObj.selfReplace("地下覈爆", "地下核爆") + strObj.selfReplace("非覈武區", "非核武區") + strObj.selfReplace("覈反應器", "核反應器") + strObj.selfReplace("覈物理學", "核物理學") + strObj.selfReplace("覈能發電", "核能發電") + strObj.selfReplace("覈能電廠", "核能電廠") + strObj.selfReplace("覈能廢料", "核能廢料") + strObj.selfReplace("覈能潛艇", "核能潛艇") + strObj.selfReplace("覈磁共振", "核磁共振") + strObj.selfReplace("熱覈反應", "熱核反應") + strObj.selfReplace("賣李鑽覈", "賣李鑽核") + strObj.selfReplace("雙覈都市", "雙核都市") + strObj.selfReplace("罵人不吐覈", "罵人不吐核") + return strObj + } - @objc class func cnvTradToJIS(_ strObj: String) -> String { - // 該轉換是由康熙繁體轉換至日語當用漢字的,所以需要先跑一遍康熙轉換。 - var strObj = cnvTradToKangXi(strObj) - strObj.selfReplace("兩", "両") - strObj.selfReplace("輛", "両") - strObj.selfReplace("辨", "弁") - strObj.selfReplace("辯", "弁") - strObj.selfReplace("瓣", "弁") - strObj.selfReplace("辦", "弁") - strObj.selfReplace("禦", "御") - strObj.selfReplace("缺", "欠") - strObj.selfReplace("絲", "糸") - strObj.selfReplace("藝", "芸") - strObj.selfReplace("濱", "浜") - strObj.selfReplace("乘", "乗") - strObj.selfReplace("亂", "乱") - strObj.selfReplace("亙", "亘") - strObj.selfReplace("亞", "亜") - strObj.selfReplace("佛", "仏") - strObj.selfReplace("來", "来") - strObj.selfReplace("假", "仮") - strObj.selfReplace("傳", "伝") - strObj.selfReplace("僞", "偽") - strObj.selfReplace("價", "価") - strObj.selfReplace("儉", "倹") - strObj.selfReplace("兒", "児") - strObj.selfReplace("內", "内") - strObj.selfReplace("剎", "刹") - strObj.selfReplace("剩", "剰") - strObj.selfReplace("劍", "剣") - strObj.selfReplace("剱", "剣") - strObj.selfReplace("劎", "剣") - strObj.selfReplace("劒", "剣") - strObj.selfReplace("劔", "剣") - strObj.selfReplace("劑", "剤") - strObj.selfReplace("勞", "労") - strObj.selfReplace("勳", "勲") - strObj.selfReplace("勵", "励") - strObj.selfReplace("勸", "勧") - strObj.selfReplace("勻", "匀") - strObj.selfReplace("區", "区") - strObj.selfReplace("卷", "巻") - strObj.selfReplace("卻", "却") - strObj.selfReplace("參", "参") - strObj.selfReplace("吳", "呉") - strObj.selfReplace("咒", "呪") - strObj.selfReplace("啞", "唖") - strObj.selfReplace("單", "単") - strObj.selfReplace("噓", "嘘") - strObj.selfReplace("嚙", "噛") - strObj.selfReplace("嚴", "厳") - strObj.selfReplace("囑", "嘱") - strObj.selfReplace("圈", "圏") - strObj.selfReplace("國", "国") - strObj.selfReplace("圍", "囲") - strObj.selfReplace("圓", "円") - strObj.selfReplace("圖", "図") - strObj.selfReplace("團", "団") - strObj.selfReplace("增", "増") - strObj.selfReplace("墮", "堕") - strObj.selfReplace("壓", "圧") - strObj.selfReplace("壘", "塁") - strObj.selfReplace("壞", "壊") - strObj.selfReplace("壤", "壌") - strObj.selfReplace("壯", "壮") - strObj.selfReplace("壹", "壱") - strObj.selfReplace("壽", "寿") - strObj.selfReplace("奧", "奥") - strObj.selfReplace("奬", "奨") - strObj.selfReplace("妝", "粧") - strObj.selfReplace("孃", "嬢") - strObj.selfReplace("學", "学") - strObj.selfReplace("寢", "寝") - strObj.selfReplace("實", "実") - strObj.selfReplace("寫", "写") - strObj.selfReplace("寬", "寛") - strObj.selfReplace("寶", "宝") - strObj.selfReplace("將", "将") - strObj.selfReplace("專", "専") - strObj.selfReplace("對", "対") - strObj.selfReplace("屆", "届") - strObj.selfReplace("屬", "属") - strObj.selfReplace("峯", "峰") - strObj.selfReplace("峽", "峡") - strObj.selfReplace("嶽", "岳") - strObj.selfReplace("巖", "巌") - strObj.selfReplace("巢", "巣") - strObj.selfReplace("帶", "帯") - strObj.selfReplace("廁", "厠") - strObj.selfReplace("廢", "廃") - strObj.selfReplace("廣", "広") - strObj.selfReplace("廳", "庁") - strObj.selfReplace("彈", "弾") - strObj.selfReplace("彌", "弥") - strObj.selfReplace("彎", "弯") - strObj.selfReplace("彥", "彦") - strObj.selfReplace("徑", "径") - strObj.selfReplace("從", "従") - strObj.selfReplace("徵", "徴") - strObj.selfReplace("德", "徳") - strObj.selfReplace("恆", "恒") - strObj.selfReplace("悅", "悦") - strObj.selfReplace("惠", "恵") - strObj.selfReplace("惡", "悪") - strObj.selfReplace("惱", "悩") - strObj.selfReplace("慘", "惨") - strObj.selfReplace("應", "応") - strObj.selfReplace("懷", "懐") - strObj.selfReplace("戀", "恋") - strObj.selfReplace("戰", "戦") - strObj.selfReplace("戲", "戯") - strObj.selfReplace("戶", "戸") - strObj.selfReplace("戾", "戻") - strObj.selfReplace("拂", "払") - strObj.selfReplace("拔", "抜") - strObj.selfReplace("拜", "拝") - strObj.selfReplace("挾", "挟") - strObj.selfReplace("插", "挿") - strObj.selfReplace("揭", "掲") - strObj.selfReplace("搔", "掻") - strObj.selfReplace("搖", "揺") - strObj.selfReplace("搜", "捜") - strObj.selfReplace("摑", "掴") - strObj.selfReplace("擇", "択") - strObj.selfReplace("擊", "撃") - strObj.selfReplace("擔", "担") - strObj.selfReplace("據", "拠") - strObj.selfReplace("擴", "拡") - strObj.selfReplace("攝", "摂") - strObj.selfReplace("攪", "撹") - strObj.selfReplace("收", "収") - strObj.selfReplace("效", "効") - strObj.selfReplace("敕", "勅") - strObj.selfReplace("敘", "叙") - strObj.selfReplace("數", "数") - strObj.selfReplace("斷", "断") - strObj.selfReplace("晉", "晋") - strObj.selfReplace("晚", "晩") - strObj.selfReplace("晝", "昼") - strObj.selfReplace("暨", "曁") - strObj.selfReplace("曆", "暦") - strObj.selfReplace("曉", "暁") - strObj.selfReplace("曾", "曽") - strObj.selfReplace("會", "会") - strObj.selfReplace("枡", "桝") - strObj.selfReplace("查", "査") - strObj.selfReplace("條", "条") - strObj.selfReplace("棧", "桟") - strObj.selfReplace("棱", "稜") - strObj.selfReplace("榆", "楡") - strObj.selfReplace("榮", "栄") - strObj.selfReplace("樂", "楽") - strObj.selfReplace("樓", "楼") - strObj.selfReplace("樞", "枢") - strObj.selfReplace("樣", "様") - strObj.selfReplace("橫", "横") - strObj.selfReplace("檢", "検") - strObj.selfReplace("櫻", "桜") - strObj.selfReplace("權", "権") - strObj.selfReplace("歐", "欧") - strObj.selfReplace("歡", "歓") - strObj.selfReplace("步", "歩") - strObj.selfReplace("歲", "歳") - strObj.selfReplace("歷", "歴") - strObj.selfReplace("歸", "帰") - strObj.selfReplace("殘", "残") - strObj.selfReplace("殼", "殻") - strObj.selfReplace("毆", "殴") - strObj.selfReplace("每", "毎") - strObj.selfReplace("氣", "気") - strObj.selfReplace("污", "汚") - strObj.selfReplace("沒", "没") - strObj.selfReplace("涉", "渉") - strObj.selfReplace("淚", "涙") - strObj.selfReplace("淨", "浄") - strObj.selfReplace("淺", "浅") - strObj.selfReplace("渴", "渇") - strObj.selfReplace("溌", "潑") - strObj.selfReplace("溪", "渓") - strObj.selfReplace("溫", "温") - strObj.selfReplace("溼", "湿") - strObj.selfReplace("滯", "滞") - strObj.selfReplace("滿", "満") - strObj.selfReplace("潛", "潜") - strObj.selfReplace("澀", "渋") - strObj.selfReplace("澤", "沢") - strObj.selfReplace("濟", "済") - strObj.selfReplace("濤", "涛") - strObj.selfReplace("濾", "沪") - strObj.selfReplace("瀧", "滝") - strObj.selfReplace("瀨", "瀬") - strObj.selfReplace("灣", "湾") - strObj.selfReplace("焰", "焔") - strObj.selfReplace("燈", "灯") - strObj.selfReplace("燒", "焼") - strObj.selfReplace("營", "営") - strObj.selfReplace("爐", "炉") - strObj.selfReplace("爭", "争") - strObj.selfReplace("爲", "為") - strObj.selfReplace("牀", "床") - strObj.selfReplace("犧", "犠") - strObj.selfReplace("狀", "状") - strObj.selfReplace("狹", "狭") - strObj.selfReplace("獨", "独") - strObj.selfReplace("獵", "猟") - strObj.selfReplace("獸", "獣") - strObj.selfReplace("獻", "献") - strObj.selfReplace("產", "産") - strObj.selfReplace("畫", "画") - strObj.selfReplace("當", "当") - strObj.selfReplace("疊", "畳") - strObj.selfReplace("疎", "疏") - strObj.selfReplace("痹", "痺") - strObj.selfReplace("瘦", "痩") - strObj.selfReplace("癡", "痴") - strObj.selfReplace("發", "発") - strObj.selfReplace("皋", "皐") - strObj.selfReplace("盜", "盗") - strObj.selfReplace("盡", "尽") - strObj.selfReplace("碎", "砕") - strObj.selfReplace("祕", "秘") - strObj.selfReplace("祿", "禄") - strObj.selfReplace("禪", "禅") - strObj.selfReplace("禮", "礼") - strObj.selfReplace("禱", "祷") - strObj.selfReplace("稅", "税") - strObj.selfReplace("稱", "称") - strObj.selfReplace("稻", "稲") - strObj.selfReplace("穎", "頴") - strObj.selfReplace("穗", "穂") - strObj.selfReplace("穩", "穏") - strObj.selfReplace("穰", "穣") - strObj.selfReplace("竃", "竈") - strObj.selfReplace("竊", "窃") - strObj.selfReplace("粹", "粋") - strObj.selfReplace("糉", "粽") - strObj.selfReplace("絕", "絶") - strObj.selfReplace("經", "経") - strObj.selfReplace("綠", "緑") - strObj.selfReplace("緖", "緒") - strObj.selfReplace("緣", "縁") - strObj.selfReplace("縣", "県") - strObj.selfReplace("縱", "縦") - strObj.selfReplace("總", "総") - strObj.selfReplace("繋", "繫") - strObj.selfReplace("繡", "繍") - strObj.selfReplace("繩", "縄") - strObj.selfReplace("繪", "絵") - strObj.selfReplace("繼", "継") - strObj.selfReplace("續", "続") - strObj.selfReplace("纔", "才") - strObj.selfReplace("纖", "繊") - strObj.selfReplace("罐", "缶") - strObj.selfReplace("羣", "群") - strObj.selfReplace("聯", "連") - strObj.selfReplace("聰", "聡") - strObj.selfReplace("聲", "声") - strObj.selfReplace("聽", "聴") - strObj.selfReplace("肅", "粛") - strObj.selfReplace("脣", "唇") - strObj.selfReplace("脫", "脱") - strObj.selfReplace("腦", "脳") - strObj.selfReplace("腳", "脚") - strObj.selfReplace("膽", "胆") - strObj.selfReplace("臟", "臓") - strObj.selfReplace("臺", "台") - strObj.selfReplace("與", "与") - strObj.selfReplace("舉", "挙") - strObj.selfReplace("舊", "旧") - strObj.selfReplace("舍", "舎") - strObj.selfReplace("荔", "茘") - strObj.selfReplace("莊", "荘") - strObj.selfReplace("莖", "茎") - strObj.selfReplace("菸", "煙") - strObj.selfReplace("萊", "莱") - strObj.selfReplace("萬", "万") - strObj.selfReplace("蔣", "蒋") - strObj.selfReplace("蔥", "葱") - strObj.selfReplace("薰", "薫") - strObj.selfReplace("藏", "蔵") - strObj.selfReplace("藥", "薬") - strObj.selfReplace("蘆", "芦") - strObj.selfReplace("處", "処") - strObj.selfReplace("虛", "虚") - strObj.selfReplace("號", "号") - strObj.selfReplace("螢", "蛍") - strObj.selfReplace("蟲", "虫") - strObj.selfReplace("蠟", "蝋") - strObj.selfReplace("蠶", "蚕") - strObj.selfReplace("蠻", "蛮") - strObj.selfReplace("裝", "装") - strObj.selfReplace("覺", "覚") - strObj.selfReplace("覽", "覧") - strObj.selfReplace("觀", "観") - strObj.selfReplace("觸", "触") - strObj.selfReplace("說", "説") - strObj.selfReplace("謠", "謡") - strObj.selfReplace("證", "証") - strObj.selfReplace("譯", "訳") - strObj.selfReplace("譽", "誉") - strObj.selfReplace("讀", "読") - strObj.selfReplace("變", "変") - strObj.selfReplace("讓", "譲") - strObj.selfReplace("豐", "豊") - strObj.selfReplace("豫", "予") - strObj.selfReplace("貓", "猫") - strObj.selfReplace("貳", "弐") - strObj.selfReplace("賣", "売") - strObj.selfReplace("賴", "頼") - strObj.selfReplace("贊", "賛") - strObj.selfReplace("贗", "贋") - strObj.selfReplace("踐", "践") - strObj.selfReplace("輕", "軽") - strObj.selfReplace("輛", "輌") - strObj.selfReplace("轉", "転") - strObj.selfReplace("辭", "辞") - strObj.selfReplace("遞", "逓") - strObj.selfReplace("遥", "遙") - strObj.selfReplace("遲", "遅") - strObj.selfReplace("邊", "辺") - strObj.selfReplace("鄉", "郷") - strObj.selfReplace("酢", "醋") - strObj.selfReplace("醉", "酔") - strObj.selfReplace("醗", "醱") - strObj.selfReplace("醫", "医") - strObj.selfReplace("醬", "醤") - strObj.selfReplace("釀", "醸") - strObj.selfReplace("釋", "釈") - strObj.selfReplace("鋪", "舗") - strObj.selfReplace("錄", "録") - strObj.selfReplace("錢", "銭") - strObj.selfReplace("鍊", "錬") - strObj.selfReplace("鐵", "鉄") - strObj.selfReplace("鑄", "鋳") - strObj.selfReplace("鑛", "鉱") - strObj.selfReplace("閱", "閲") - strObj.selfReplace("關", "関") - strObj.selfReplace("陷", "陥") - strObj.selfReplace("隨", "随") - strObj.selfReplace("險", "険") - strObj.selfReplace("隱", "隠") - strObj.selfReplace("雙", "双") - strObj.selfReplace("雜", "雑") - strObj.selfReplace("雞", "鶏") - strObj.selfReplace("霸", "覇") - strObj.selfReplace("靈", "霊") - strObj.selfReplace("靜", "静") - strObj.selfReplace("顏", "顔") - strObj.selfReplace("顯", "顕") - strObj.selfReplace("餘", "余") - strObj.selfReplace("騷", "騒") - strObj.selfReplace("驅", "駆") - strObj.selfReplace("驗", "験") - strObj.selfReplace("驛", "駅") - strObj.selfReplace("髓", "髄") - strObj.selfReplace("體", "体") - strObj.selfReplace("髮", "髪") - strObj.selfReplace("鬥", "闘") - strObj.selfReplace("鱉", "鼈") - strObj.selfReplace("鷗", "鴎") - strObj.selfReplace("鹼", "鹸") - strObj.selfReplace("鹽", "塩") - strObj.selfReplace("麥", "麦") - strObj.selfReplace("麪", "麺") - strObj.selfReplace("麴", "麹") - strObj.selfReplace("黃", "黄") - strObj.selfReplace("黑", "黒") - strObj.selfReplace("默", "黙") - strObj.selfReplace("點", "点") - strObj.selfReplace("黨", "党") - strObj.selfReplace("齊", "斉") - strObj.selfReplace("齋", "斎") - strObj.selfReplace("齒", "歯") - strObj.selfReplace("齡", "齢") - strObj.selfReplace("龍", "竜") - strObj.selfReplace("龜", "亀") - strObj.selfReplace("叮嚀", "丁寧") - strObj.selfReplace("鄭重", "丁重") - strObj.selfReplace("輿論", "世論") - strObj.selfReplace("唖鈴", "亜鈴") - strObj.selfReplace("交叉", "交差") - strObj.selfReplace("饗宴", "供宴") - strObj.selfReplace("駿馬", "俊馬") - strObj.selfReplace("堡塁", "保塁") - strObj.selfReplace("扁平", "偏平") - strObj.selfReplace("碇泊", "停泊") - strObj.selfReplace("優駿", "優俊") - strObj.selfReplace("尖兵", "先兵") - strObj.selfReplace("尖鋭", "先鋭") - strObj.selfReplace("共軛", "共役") - strObj.selfReplace("饒舌", "冗舌") - strObj.selfReplace("兇器", "凶器") - strObj.selfReplace("鑿岩", "削岩") - strObj.selfReplace("庖丁", "包丁") - strObj.selfReplace("繃帯", "包帯") - strObj.selfReplace("区劃", "区画") - strObj.selfReplace("儼然", "厳然") - strObj.selfReplace("友誼", "友宜") - strObj.selfReplace("叛乱", "反乱") - strObj.selfReplace("蒐集", "収集") - strObj.selfReplace("抒情", "叙情") - strObj.selfReplace("擡頭", "台頭") - strObj.selfReplace("合弁", "合弁") - strObj.selfReplace("歎願", "嘆願") - strObj.selfReplace("廻転", "回転") - strObj.selfReplace("回游", "回遊") - strObj.selfReplace("捧持", "奉持") - strObj.selfReplace("萎縮", "委縮") - strObj.selfReplace("輾転", "展転") - strObj.selfReplace("稀少", "希少") - strObj.selfReplace("眩惑", "幻惑") - strObj.selfReplace("広汎", "広範") - strObj.selfReplace("曠野", "広野") - strObj.selfReplace("廃墟", "廃虚") - strObj.selfReplace("弁当", "弁当") - strObj.selfReplace("弁膜", "弁膜") - strObj.selfReplace("弁護", "弁護") - strObj.selfReplace("辮髪", "弁髪") - strObj.selfReplace("絃歌", "弦歌") - strObj.selfReplace("恩誼", "恩義") - strObj.selfReplace("意嚮", "意向") - strObj.selfReplace("臆断", "憶断") - strObj.selfReplace("臆病", "憶病") - strObj.selfReplace("戦歿", "戦没") - strObj.selfReplace("煽情", "扇情") - strObj.selfReplace("手帖", "手帳") - strObj.selfReplace("伎倆", "技量") - strObj.selfReplace("抜萃", "抜粋") - strObj.selfReplace("披瀝", "披歴") - strObj.selfReplace("牴触", "抵触") - strObj.selfReplace("抽籤", "抽選") - strObj.selfReplace("勾引", "拘引") - strObj.selfReplace("醵出", "拠出") - strObj.selfReplace("醵金", "拠金") - strObj.selfReplace("掘鑿", "掘削") - strObj.selfReplace("扣除", "控除") - strObj.selfReplace("掩護", "援護") - strObj.selfReplace("抛棄", "放棄") - strObj.selfReplace("撒水", "散水") - strObj.selfReplace("敬虔", "敬謙") - strObj.selfReplace("敷衍", "敷延") - strObj.selfReplace("断乎", "断固") - strObj.selfReplace("簇生", "族生") - strObj.selfReplace("陞叙", "昇叙") - strObj.selfReplace("煖房", "暖房") - strObj.selfReplace("暗誦", "暗唱") - strObj.selfReplace("闇夜", "暗夜") - strObj.selfReplace("曝露", "暴露") - strObj.selfReplace("涸渇", "枯渇") - strObj.selfReplace("恰好", "格好") - strObj.selfReplace("恰幅", "格幅") - strObj.selfReplace("毀損", "棄損") - strObj.selfReplace("摸索", "模索") - strObj.selfReplace("欠欠", "欠缺") - strObj.selfReplace("屍体", "死体") - strObj.selfReplace("臀部", "殿部") - strObj.selfReplace("拇指", "母指") - strObj.selfReplace("気魄", "気迫") - strObj.selfReplace("訣別", "決別") - strObj.selfReplace("決潰", "決壊") - strObj.selfReplace("沈澱", "沈殿") - strObj.selfReplace("波瀾", "波乱") - strObj.selfReplace("註釈", "注釈") - strObj.selfReplace("洗滌", "洗濯") - strObj.selfReplace("活潑", "活発") - strObj.selfReplace("滲透", "浸透") - strObj.selfReplace("浸蝕", "浸食") - strObj.selfReplace("銷却", "消却") - strObj.selfReplace("渾然", "混然") - strObj.selfReplace("弯曲", "湾曲") - strObj.selfReplace("熔接", "溶接") - strObj.selfReplace("漁撈", "漁労") - strObj.selfReplace("飄然", "漂然") - strObj.selfReplace("激昂", "激高") - strObj.selfReplace("火焔", "火炎") - strObj.selfReplace("焦躁", "焦燥") - strObj.selfReplace("斑点", "班点") - strObj.selfReplace("溜飲", "留飲") - strObj.selfReplace("掠奪", "略奪") - strObj.selfReplace("疏通", "疎通") - strObj.selfReplace("醱酵", "発酵") - strObj.selfReplace("白堊", "白亜") - strObj.selfReplace("相剋", "相克") - strObj.selfReplace("智慧", "知恵") - strObj.selfReplace("破毀", "破棄") - strObj.selfReplace("確乎", "確固") - strObj.selfReplace("禁錮", "禁固") - strObj.selfReplace("符牒", "符丁") - strObj.selfReplace("扮装", "粉装") - strObj.selfReplace("紫斑", "紫班") - strObj.selfReplace("終熄", "終息") - strObj.selfReplace("綜合", "総合") - strObj.selfReplace("編輯", "編集") - strObj.selfReplace("義捐", "義援") - strObj.selfReplace("肝腎", "肝心") - strObj.selfReplace("悖徳", "背徳") - strObj.selfReplace("脈搏", "脈拍") - strObj.selfReplace("膨脹", "膨張") - strObj.selfReplace("芳醇", "芳純") - strObj.selfReplace("叡智", "英知") - strObj.selfReplace("蒸溜", "蒸留") - strObj.selfReplace("燻蒸", "薫蒸") - strObj.selfReplace("燻製", "薫製") - strObj.selfReplace("衣裳", "衣装") - strObj.selfReplace("衰頽", "衰退") - strObj.selfReplace("悠然", "裕然") - strObj.selfReplace("輔佐", "補佐") - strObj.selfReplace("訓誡", "訓戒") - strObj.selfReplace("試煉", "試練") - strObj.selfReplace("詭弁", "詭弁") - strObj.selfReplace("媾和", "講和") - strObj.selfReplace("象嵌", "象眼") - strObj.selfReplace("貫禄", "貫録") - strObj.selfReplace("買弁", "買弁") - strObj.selfReplace("讚辞", "賛辞") - strObj.selfReplace("蹈襲", "踏襲") - strObj.selfReplace("車両", "車両") - strObj.selfReplace("顛倒", "転倒") - strObj.selfReplace("輪廓", "輪郭") - strObj.selfReplace("褪色", "退色") - strObj.selfReplace("杜絶", "途絶") - strObj.selfReplace("連繫", "連係") - strObj.selfReplace("連合", "連合") - strObj.selfReplace("銓衡", "選考") - strObj.selfReplace("醋酸", "酢酸") - strObj.selfReplace("野鄙", "野卑") - strObj.selfReplace("礦石", "鉱石") - strObj.selfReplace("間歇", "間欠") - strObj.selfReplace("函数", "関数") - strObj.selfReplace("防御", "防御") - strObj.selfReplace("嶮岨", "険阻") - strObj.selfReplace("牆壁", "障壁") - strObj.selfReplace("障礙", "障害") - strObj.selfReplace("湮滅", "隠滅") - strObj.selfReplace("聚落", "集落") - strObj.selfReplace("雇傭", "雇用") - strObj.selfReplace("諷喩", "風諭") - strObj.selfReplace("蜚語", "飛語") - strObj.selfReplace("香奠", "香典") - strObj.selfReplace("骨骼", "骨格") - strObj.selfReplace("亢進", "高進") - strObj.selfReplace("鳥瞰", "鳥観") - strObj.selfReplace("一攫", "一獲") - strObj.selfReplace("肩胛", "肩甲") - strObj.selfReplace("箇条", "個条") - strObj.selfReplace("啓動", "起動") - strObj.selfReplace("三叉路", "三差路") - strObj.selfReplace("嬉遊曲", "喜遊曲") - strObj.selfReplace("建蔽率", "建坪率") - strObj.selfReplace("慰藉料", "慰謝料") - strObj.selfReplace("橋頭堡", "橋頭保") - strObj.selfReplace("油槽船", "油送船") - strObj.selfReplace("耕耘機", "耕運機") - return strObj - } + class func cnvTradToJIS(_ strObj: String) -> String { + // 該轉換是由康熙繁體轉換至日語當用漢字的,所以需要先跑一遍康熙轉換。 + var strObj = cnvTradToKangXi(strObj) + strObj.selfReplace("兩", "両") + strObj.selfReplace("輛", "両") + strObj.selfReplace("辨", "弁") + strObj.selfReplace("辯", "弁") + strObj.selfReplace("瓣", "弁") + strObj.selfReplace("辦", "弁") + strObj.selfReplace("禦", "御") + strObj.selfReplace("缺", "欠") + strObj.selfReplace("絲", "糸") + strObj.selfReplace("藝", "芸") + strObj.selfReplace("濱", "浜") + strObj.selfReplace("乘", "乗") + strObj.selfReplace("亂", "乱") + strObj.selfReplace("亙", "亘") + strObj.selfReplace("亞", "亜") + strObj.selfReplace("佛", "仏") + strObj.selfReplace("來", "来") + strObj.selfReplace("假", "仮") + strObj.selfReplace("傳", "伝") + strObj.selfReplace("僞", "偽") + strObj.selfReplace("價", "価") + strObj.selfReplace("儉", "倹") + strObj.selfReplace("兒", "児") + strObj.selfReplace("內", "内") + strObj.selfReplace("剎", "刹") + strObj.selfReplace("剩", "剰") + strObj.selfReplace("劍", "剣") + strObj.selfReplace("剱", "剣") + strObj.selfReplace("劎", "剣") + strObj.selfReplace("劒", "剣") + strObj.selfReplace("劔", "剣") + strObj.selfReplace("劑", "剤") + strObj.selfReplace("勞", "労") + strObj.selfReplace("勳", "勲") + strObj.selfReplace("勵", "励") + strObj.selfReplace("勸", "勧") + strObj.selfReplace("勻", "匀") + strObj.selfReplace("區", "区") + strObj.selfReplace("卷", "巻") + strObj.selfReplace("卻", "却") + strObj.selfReplace("參", "参") + strObj.selfReplace("吳", "呉") + strObj.selfReplace("咒", "呪") + strObj.selfReplace("啞", "唖") + strObj.selfReplace("單", "単") + strObj.selfReplace("噓", "嘘") + strObj.selfReplace("嚙", "噛") + strObj.selfReplace("嚴", "厳") + strObj.selfReplace("囑", "嘱") + strObj.selfReplace("圈", "圏") + strObj.selfReplace("國", "国") + strObj.selfReplace("圍", "囲") + strObj.selfReplace("圓", "円") + strObj.selfReplace("圖", "図") + strObj.selfReplace("團", "団") + strObj.selfReplace("增", "増") + strObj.selfReplace("墮", "堕") + strObj.selfReplace("壓", "圧") + strObj.selfReplace("壘", "塁") + strObj.selfReplace("壞", "壊") + strObj.selfReplace("壤", "壌") + strObj.selfReplace("壯", "壮") + strObj.selfReplace("壹", "壱") + strObj.selfReplace("壽", "寿") + strObj.selfReplace("奧", "奥") + strObj.selfReplace("奬", "奨") + strObj.selfReplace("妝", "粧") + strObj.selfReplace("孃", "嬢") + strObj.selfReplace("學", "学") + strObj.selfReplace("寢", "寝") + strObj.selfReplace("實", "実") + strObj.selfReplace("寫", "写") + strObj.selfReplace("寬", "寛") + strObj.selfReplace("寶", "宝") + strObj.selfReplace("將", "将") + strObj.selfReplace("專", "専") + strObj.selfReplace("對", "対") + strObj.selfReplace("屆", "届") + strObj.selfReplace("屬", "属") + strObj.selfReplace("峯", "峰") + strObj.selfReplace("峽", "峡") + strObj.selfReplace("嶽", "岳") + strObj.selfReplace("巖", "巌") + strObj.selfReplace("巢", "巣") + strObj.selfReplace("帶", "帯") + strObj.selfReplace("廁", "厠") + strObj.selfReplace("廢", "廃") + strObj.selfReplace("廣", "広") + strObj.selfReplace("廳", "庁") + strObj.selfReplace("彈", "弾") + strObj.selfReplace("彌", "弥") + strObj.selfReplace("彎", "弯") + strObj.selfReplace("彥", "彦") + strObj.selfReplace("徑", "径") + strObj.selfReplace("從", "従") + strObj.selfReplace("徵", "徴") + strObj.selfReplace("德", "徳") + strObj.selfReplace("恆", "恒") + strObj.selfReplace("悅", "悦") + strObj.selfReplace("惠", "恵") + strObj.selfReplace("惡", "悪") + strObj.selfReplace("惱", "悩") + strObj.selfReplace("慘", "惨") + strObj.selfReplace("應", "応") + strObj.selfReplace("懷", "懐") + strObj.selfReplace("戀", "恋") + strObj.selfReplace("戰", "戦") + strObj.selfReplace("戲", "戯") + strObj.selfReplace("戶", "戸") + strObj.selfReplace("戾", "戻") + strObj.selfReplace("拂", "払") + strObj.selfReplace("拔", "抜") + strObj.selfReplace("拜", "拝") + strObj.selfReplace("挾", "挟") + strObj.selfReplace("插", "挿") + strObj.selfReplace("揭", "掲") + strObj.selfReplace("搔", "掻") + strObj.selfReplace("搖", "揺") + strObj.selfReplace("搜", "捜") + strObj.selfReplace("摑", "掴") + strObj.selfReplace("擇", "択") + strObj.selfReplace("擊", "撃") + strObj.selfReplace("擔", "担") + strObj.selfReplace("據", "拠") + strObj.selfReplace("擴", "拡") + strObj.selfReplace("攝", "摂") + strObj.selfReplace("攪", "撹") + strObj.selfReplace("收", "収") + strObj.selfReplace("效", "効") + strObj.selfReplace("敕", "勅") + strObj.selfReplace("敘", "叙") + strObj.selfReplace("數", "数") + strObj.selfReplace("斷", "断") + strObj.selfReplace("晉", "晋") + strObj.selfReplace("晚", "晩") + strObj.selfReplace("晝", "昼") + strObj.selfReplace("暨", "曁") + strObj.selfReplace("曆", "暦") + strObj.selfReplace("曉", "暁") + strObj.selfReplace("曾", "曽") + strObj.selfReplace("會", "会") + strObj.selfReplace("枡", "桝") + strObj.selfReplace("查", "査") + strObj.selfReplace("條", "条") + strObj.selfReplace("棧", "桟") + strObj.selfReplace("棱", "稜") + strObj.selfReplace("榆", "楡") + strObj.selfReplace("榮", "栄") + strObj.selfReplace("樂", "楽") + strObj.selfReplace("樓", "楼") + strObj.selfReplace("樞", "枢") + strObj.selfReplace("樣", "様") + strObj.selfReplace("橫", "横") + strObj.selfReplace("檢", "検") + strObj.selfReplace("櫻", "桜") + strObj.selfReplace("權", "権") + strObj.selfReplace("歐", "欧") + strObj.selfReplace("歡", "歓") + strObj.selfReplace("步", "歩") + strObj.selfReplace("歲", "歳") + strObj.selfReplace("歷", "歴") + strObj.selfReplace("歸", "帰") + strObj.selfReplace("殘", "残") + strObj.selfReplace("殼", "殻") + strObj.selfReplace("毆", "殴") + strObj.selfReplace("每", "毎") + strObj.selfReplace("氣", "気") + strObj.selfReplace("污", "汚") + strObj.selfReplace("沒", "没") + strObj.selfReplace("涉", "渉") + strObj.selfReplace("淚", "涙") + strObj.selfReplace("淨", "浄") + strObj.selfReplace("淺", "浅") + strObj.selfReplace("渴", "渇") + strObj.selfReplace("溌", "潑") + strObj.selfReplace("溪", "渓") + strObj.selfReplace("溫", "温") + strObj.selfReplace("溼", "湿") + strObj.selfReplace("滯", "滞") + strObj.selfReplace("滿", "満") + strObj.selfReplace("潛", "潜") + strObj.selfReplace("澀", "渋") + strObj.selfReplace("澤", "沢") + strObj.selfReplace("濟", "済") + strObj.selfReplace("濤", "涛") + strObj.selfReplace("濾", "沪") + strObj.selfReplace("瀧", "滝") + strObj.selfReplace("瀨", "瀬") + strObj.selfReplace("灣", "湾") + strObj.selfReplace("焰", "焔") + strObj.selfReplace("燈", "灯") + strObj.selfReplace("燒", "焼") + strObj.selfReplace("營", "営") + strObj.selfReplace("爐", "炉") + strObj.selfReplace("爭", "争") + strObj.selfReplace("爲", "為") + strObj.selfReplace("牀", "床") + strObj.selfReplace("犧", "犠") + strObj.selfReplace("狀", "状") + strObj.selfReplace("狹", "狭") + strObj.selfReplace("獨", "独") + strObj.selfReplace("獵", "猟") + strObj.selfReplace("獸", "獣") + strObj.selfReplace("獻", "献") + strObj.selfReplace("產", "産") + strObj.selfReplace("畫", "画") + strObj.selfReplace("當", "当") + strObj.selfReplace("疊", "畳") + strObj.selfReplace("疎", "疏") + strObj.selfReplace("痹", "痺") + strObj.selfReplace("瘦", "痩") + strObj.selfReplace("癡", "痴") + strObj.selfReplace("發", "発") + strObj.selfReplace("皋", "皐") + strObj.selfReplace("盜", "盗") + strObj.selfReplace("盡", "尽") + strObj.selfReplace("碎", "砕") + strObj.selfReplace("祕", "秘") + strObj.selfReplace("祿", "禄") + strObj.selfReplace("禪", "禅") + strObj.selfReplace("禮", "礼") + strObj.selfReplace("禱", "祷") + strObj.selfReplace("稅", "税") + strObj.selfReplace("稱", "称") + strObj.selfReplace("稻", "稲") + strObj.selfReplace("穎", "頴") + strObj.selfReplace("穗", "穂") + strObj.selfReplace("穩", "穏") + strObj.selfReplace("穰", "穣") + strObj.selfReplace("竃", "竈") + strObj.selfReplace("竊", "窃") + strObj.selfReplace("粹", "粋") + strObj.selfReplace("糉", "粽") + strObj.selfReplace("絕", "絶") + strObj.selfReplace("經", "経") + strObj.selfReplace("綠", "緑") + strObj.selfReplace("緖", "緒") + strObj.selfReplace("緣", "縁") + strObj.selfReplace("縣", "県") + strObj.selfReplace("縱", "縦") + strObj.selfReplace("總", "総") + strObj.selfReplace("繋", "繫") + strObj.selfReplace("繡", "繍") + strObj.selfReplace("繩", "縄") + strObj.selfReplace("繪", "絵") + strObj.selfReplace("繼", "継") + strObj.selfReplace("續", "続") + strObj.selfReplace("纔", "才") + strObj.selfReplace("纖", "繊") + strObj.selfReplace("罐", "缶") + strObj.selfReplace("羣", "群") + strObj.selfReplace("聯", "連") + strObj.selfReplace("聰", "聡") + strObj.selfReplace("聲", "声") + strObj.selfReplace("聽", "聴") + strObj.selfReplace("肅", "粛") + strObj.selfReplace("脣", "唇") + strObj.selfReplace("脫", "脱") + strObj.selfReplace("腦", "脳") + strObj.selfReplace("腳", "脚") + strObj.selfReplace("膽", "胆") + strObj.selfReplace("臟", "臓") + strObj.selfReplace("臺", "台") + strObj.selfReplace("與", "与") + strObj.selfReplace("舉", "挙") + strObj.selfReplace("舊", "旧") + strObj.selfReplace("舍", "舎") + strObj.selfReplace("荔", "茘") + strObj.selfReplace("莊", "荘") + strObj.selfReplace("莖", "茎") + strObj.selfReplace("菸", "煙") + strObj.selfReplace("萊", "莱") + strObj.selfReplace("萬", "万") + strObj.selfReplace("蔣", "蒋") + strObj.selfReplace("蔥", "葱") + strObj.selfReplace("薰", "薫") + strObj.selfReplace("藏", "蔵") + strObj.selfReplace("藥", "薬") + strObj.selfReplace("蘆", "芦") + strObj.selfReplace("處", "処") + strObj.selfReplace("虛", "虚") + strObj.selfReplace("號", "号") + strObj.selfReplace("螢", "蛍") + strObj.selfReplace("蟲", "虫") + strObj.selfReplace("蠟", "蝋") + strObj.selfReplace("蠶", "蚕") + strObj.selfReplace("蠻", "蛮") + strObj.selfReplace("裝", "装") + strObj.selfReplace("覺", "覚") + strObj.selfReplace("覽", "覧") + strObj.selfReplace("觀", "観") + strObj.selfReplace("觸", "触") + strObj.selfReplace("說", "説") + strObj.selfReplace("謠", "謡") + strObj.selfReplace("證", "証") + strObj.selfReplace("譯", "訳") + strObj.selfReplace("譽", "誉") + strObj.selfReplace("讀", "読") + strObj.selfReplace("變", "変") + strObj.selfReplace("讓", "譲") + strObj.selfReplace("豐", "豊") + strObj.selfReplace("豫", "予") + strObj.selfReplace("貓", "猫") + strObj.selfReplace("貳", "弐") + strObj.selfReplace("賣", "売") + strObj.selfReplace("賴", "頼") + strObj.selfReplace("贊", "賛") + strObj.selfReplace("贗", "贋") + strObj.selfReplace("踐", "践") + strObj.selfReplace("輕", "軽") + strObj.selfReplace("輛", "輌") + strObj.selfReplace("轉", "転") + strObj.selfReplace("辭", "辞") + strObj.selfReplace("遞", "逓") + strObj.selfReplace("遥", "遙") + strObj.selfReplace("遲", "遅") + strObj.selfReplace("邊", "辺") + strObj.selfReplace("鄉", "郷") + strObj.selfReplace("酢", "醋") + strObj.selfReplace("醉", "酔") + strObj.selfReplace("醗", "醱") + strObj.selfReplace("醫", "医") + strObj.selfReplace("醬", "醤") + strObj.selfReplace("釀", "醸") + strObj.selfReplace("釋", "釈") + strObj.selfReplace("鋪", "舗") + strObj.selfReplace("錄", "録") + strObj.selfReplace("錢", "銭") + strObj.selfReplace("鍊", "錬") + strObj.selfReplace("鐵", "鉄") + strObj.selfReplace("鑄", "鋳") + strObj.selfReplace("鑛", "鉱") + strObj.selfReplace("閱", "閲") + strObj.selfReplace("關", "関") + strObj.selfReplace("陷", "陥") + strObj.selfReplace("隨", "随") + strObj.selfReplace("險", "険") + strObj.selfReplace("隱", "隠") + strObj.selfReplace("雙", "双") + strObj.selfReplace("雜", "雑") + strObj.selfReplace("雞", "鶏") + strObj.selfReplace("霸", "覇") + strObj.selfReplace("靈", "霊") + strObj.selfReplace("靜", "静") + strObj.selfReplace("顏", "顔") + strObj.selfReplace("顯", "顕") + strObj.selfReplace("餘", "余") + strObj.selfReplace("騷", "騒") + strObj.selfReplace("驅", "駆") + strObj.selfReplace("驗", "験") + strObj.selfReplace("驛", "駅") + strObj.selfReplace("髓", "髄") + strObj.selfReplace("體", "体") + strObj.selfReplace("髮", "髪") + strObj.selfReplace("鬥", "闘") + strObj.selfReplace("鱉", "鼈") + strObj.selfReplace("鷗", "鴎") + strObj.selfReplace("鹼", "鹸") + strObj.selfReplace("鹽", "塩") + strObj.selfReplace("麥", "麦") + strObj.selfReplace("麪", "麺") + strObj.selfReplace("麴", "麹") + strObj.selfReplace("黃", "黄") + strObj.selfReplace("黑", "黒") + strObj.selfReplace("默", "黙") + strObj.selfReplace("點", "点") + strObj.selfReplace("黨", "党") + strObj.selfReplace("齊", "斉") + strObj.selfReplace("齋", "斎") + strObj.selfReplace("齒", "歯") + strObj.selfReplace("齡", "齢") + strObj.selfReplace("龍", "竜") + strObj.selfReplace("龜", "亀") + strObj.selfReplace("叮嚀", "丁寧") + strObj.selfReplace("鄭重", "丁重") + strObj.selfReplace("輿論", "世論") + strObj.selfReplace("唖鈴", "亜鈴") + strObj.selfReplace("交叉", "交差") + strObj.selfReplace("饗宴", "供宴") + strObj.selfReplace("駿馬", "俊馬") + strObj.selfReplace("堡塁", "保塁") + strObj.selfReplace("扁平", "偏平") + strObj.selfReplace("碇泊", "停泊") + strObj.selfReplace("優駿", "優俊") + strObj.selfReplace("尖兵", "先兵") + strObj.selfReplace("尖鋭", "先鋭") + strObj.selfReplace("共軛", "共役") + strObj.selfReplace("饒舌", "冗舌") + strObj.selfReplace("兇器", "凶器") + strObj.selfReplace("鑿岩", "削岩") + strObj.selfReplace("庖丁", "包丁") + strObj.selfReplace("繃帯", "包帯") + strObj.selfReplace("区劃", "区画") + strObj.selfReplace("儼然", "厳然") + strObj.selfReplace("友誼", "友宜") + strObj.selfReplace("叛乱", "反乱") + strObj.selfReplace("蒐集", "収集") + strObj.selfReplace("抒情", "叙情") + strObj.selfReplace("擡頭", "台頭") + strObj.selfReplace("合弁", "合弁") + strObj.selfReplace("歎願", "嘆願") + strObj.selfReplace("廻転", "回転") + strObj.selfReplace("回游", "回遊") + strObj.selfReplace("捧持", "奉持") + strObj.selfReplace("萎縮", "委縮") + strObj.selfReplace("輾転", "展転") + strObj.selfReplace("稀少", "希少") + strObj.selfReplace("眩惑", "幻惑") + strObj.selfReplace("広汎", "広範") + strObj.selfReplace("曠野", "広野") + strObj.selfReplace("廃墟", "廃虚") + strObj.selfReplace("弁当", "弁当") + strObj.selfReplace("弁膜", "弁膜") + strObj.selfReplace("弁護", "弁護") + strObj.selfReplace("辮髪", "弁髪") + strObj.selfReplace("絃歌", "弦歌") + strObj.selfReplace("恩誼", "恩義") + strObj.selfReplace("意嚮", "意向") + strObj.selfReplace("臆断", "憶断") + strObj.selfReplace("臆病", "憶病") + strObj.selfReplace("戦歿", "戦没") + strObj.selfReplace("煽情", "扇情") + strObj.selfReplace("手帖", "手帳") + strObj.selfReplace("伎倆", "技量") + strObj.selfReplace("抜萃", "抜粋") + strObj.selfReplace("披瀝", "披歴") + strObj.selfReplace("牴触", "抵触") + strObj.selfReplace("抽籤", "抽選") + strObj.selfReplace("勾引", "拘引") + strObj.selfReplace("醵出", "拠出") + strObj.selfReplace("醵金", "拠金") + strObj.selfReplace("掘鑿", "掘削") + strObj.selfReplace("扣除", "控除") + strObj.selfReplace("掩護", "援護") + strObj.selfReplace("抛棄", "放棄") + strObj.selfReplace("撒水", "散水") + strObj.selfReplace("敬虔", "敬謙") + strObj.selfReplace("敷衍", "敷延") + strObj.selfReplace("断乎", "断固") + strObj.selfReplace("簇生", "族生") + strObj.selfReplace("陞叙", "昇叙") + strObj.selfReplace("煖房", "暖房") + strObj.selfReplace("暗誦", "暗唱") + strObj.selfReplace("闇夜", "暗夜") + strObj.selfReplace("曝露", "暴露") + strObj.selfReplace("涸渇", "枯渇") + strObj.selfReplace("恰好", "格好") + strObj.selfReplace("恰幅", "格幅") + strObj.selfReplace("毀損", "棄損") + strObj.selfReplace("摸索", "模索") + strObj.selfReplace("欠欠", "欠缺") + strObj.selfReplace("屍体", "死体") + strObj.selfReplace("臀部", "殿部") + strObj.selfReplace("拇指", "母指") + strObj.selfReplace("気魄", "気迫") + strObj.selfReplace("訣別", "決別") + strObj.selfReplace("決潰", "決壊") + strObj.selfReplace("沈澱", "沈殿") + strObj.selfReplace("波瀾", "波乱") + strObj.selfReplace("註釈", "注釈") + strObj.selfReplace("洗滌", "洗濯") + strObj.selfReplace("活潑", "活発") + strObj.selfReplace("滲透", "浸透") + strObj.selfReplace("浸蝕", "浸食") + strObj.selfReplace("銷却", "消却") + strObj.selfReplace("渾然", "混然") + strObj.selfReplace("弯曲", "湾曲") + strObj.selfReplace("熔接", "溶接") + strObj.selfReplace("漁撈", "漁労") + strObj.selfReplace("飄然", "漂然") + strObj.selfReplace("激昂", "激高") + strObj.selfReplace("火焔", "火炎") + strObj.selfReplace("焦躁", "焦燥") + strObj.selfReplace("斑点", "班点") + strObj.selfReplace("溜飲", "留飲") + strObj.selfReplace("掠奪", "略奪") + strObj.selfReplace("疏通", "疎通") + strObj.selfReplace("醱酵", "発酵") + strObj.selfReplace("白堊", "白亜") + strObj.selfReplace("相剋", "相克") + strObj.selfReplace("智慧", "知恵") + strObj.selfReplace("破毀", "破棄") + strObj.selfReplace("確乎", "確固") + strObj.selfReplace("禁錮", "禁固") + strObj.selfReplace("符牒", "符丁") + strObj.selfReplace("扮装", "粉装") + strObj.selfReplace("紫斑", "紫班") + strObj.selfReplace("終熄", "終息") + strObj.selfReplace("綜合", "総合") + strObj.selfReplace("編輯", "編集") + strObj.selfReplace("義捐", "義援") + strObj.selfReplace("肝腎", "肝心") + strObj.selfReplace("悖徳", "背徳") + strObj.selfReplace("脈搏", "脈拍") + strObj.selfReplace("膨脹", "膨張") + strObj.selfReplace("芳醇", "芳純") + strObj.selfReplace("叡智", "英知") + strObj.selfReplace("蒸溜", "蒸留") + strObj.selfReplace("燻蒸", "薫蒸") + strObj.selfReplace("燻製", "薫製") + strObj.selfReplace("衣裳", "衣装") + strObj.selfReplace("衰頽", "衰退") + strObj.selfReplace("悠然", "裕然") + strObj.selfReplace("輔佐", "補佐") + strObj.selfReplace("訓誡", "訓戒") + strObj.selfReplace("試煉", "試練") + strObj.selfReplace("詭弁", "詭弁") + strObj.selfReplace("媾和", "講和") + strObj.selfReplace("象嵌", "象眼") + strObj.selfReplace("貫禄", "貫録") + strObj.selfReplace("買弁", "買弁") + strObj.selfReplace("讚辞", "賛辞") + strObj.selfReplace("蹈襲", "踏襲") + strObj.selfReplace("車両", "車両") + strObj.selfReplace("顛倒", "転倒") + strObj.selfReplace("輪廓", "輪郭") + strObj.selfReplace("褪色", "退色") + strObj.selfReplace("杜絶", "途絶") + strObj.selfReplace("連繫", "連係") + strObj.selfReplace("連合", "連合") + strObj.selfReplace("銓衡", "選考") + strObj.selfReplace("醋酸", "酢酸") + strObj.selfReplace("野鄙", "野卑") + strObj.selfReplace("礦石", "鉱石") + strObj.selfReplace("間歇", "間欠") + strObj.selfReplace("函数", "関数") + strObj.selfReplace("防御", "防御") + strObj.selfReplace("嶮岨", "険阻") + strObj.selfReplace("牆壁", "障壁") + strObj.selfReplace("障礙", "障害") + strObj.selfReplace("湮滅", "隠滅") + strObj.selfReplace("聚落", "集落") + strObj.selfReplace("雇傭", "雇用") + strObj.selfReplace("諷喩", "風諭") + strObj.selfReplace("蜚語", "飛語") + strObj.selfReplace("香奠", "香典") + strObj.selfReplace("骨骼", "骨格") + strObj.selfReplace("亢進", "高進") + strObj.selfReplace("鳥瞰", "鳥観") + strObj.selfReplace("一攫", "一獲") + strObj.selfReplace("肩胛", "肩甲") + strObj.selfReplace("箇条", "個条") + strObj.selfReplace("啓動", "起動") + strObj.selfReplace("三叉路", "三差路") + strObj.selfReplace("嬉遊曲", "喜遊曲") + strObj.selfReplace("建蔽率", "建坪率") + strObj.selfReplace("慰藉料", "慰謝料") + strObj.selfReplace("橋頭堡", "橋頭保") + strObj.selfReplace("油槽船", "油送船") + strObj.selfReplace("耕耘機", "耕運機") + return strObj + } } diff --git a/Source/Modules/FileHandlers/FSEventStreamHelper.swift b/Source/Modules/FileHandlers/FSEventStreamHelper.swift index 14d19dab..9cff0253 100644 --- a/Source/Modules/FileHandlers/FSEventStreamHelper.swift +++ b/Source/Modules/FileHandlers/FSEventStreamHelper.swift @@ -27,78 +27,78 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import Cocoa public protocol FSEventStreamHelperDelegate: AnyObject { - func helper(_ helper: FSEventStreamHelper, didReceive events: [FSEventStreamHelper.Event]) + func helper(_ helper: FSEventStreamHelper, didReceive events: [FSEventStreamHelper.Event]) } public class FSEventStreamHelper: NSObject { - public struct Event { - var path: String - var flags: FSEventStreamEventFlags - var id: FSEventStreamEventId - } + public struct Event { + var path: String + var flags: FSEventStreamEventFlags + var id: FSEventStreamEventId + } - public let path: String - public let dispatchQueue: DispatchQueue - public weak var delegate: FSEventStreamHelperDelegate? + public let path: String + public let dispatchQueue: DispatchQueue + public weak var delegate: FSEventStreamHelperDelegate? - @objc public init(path: String, queue: DispatchQueue) { - self.path = path - dispatchQueue = queue - } + @objc public init(path: String, queue: DispatchQueue) { + self.path = path + dispatchQueue = queue + } - private var stream: FSEventStreamRef? + private var stream: FSEventStreamRef? - public func start() -> Bool { - if stream != nil { - return false - } - var context = FSEventStreamContext() - context.info = Unmanaged.passUnretained(self).toOpaque() - guard - let stream = FSEventStreamCreate( - nil, - { - _, clientCallBackInfo, eventCount, eventPaths, eventFlags, eventIds in - let helper = Unmanaged.fromOpaque(clientCallBackInfo!) - .takeUnretainedValue() - let pathsBase = eventPaths.assumingMemoryBound(to: UnsafePointer.self) - let pathsPtr = UnsafeBufferPointer(start: pathsBase, count: eventCount) - let flagsPtr = UnsafeBufferPointer(start: eventFlags, count: eventCount) - let eventIDsPtr = UnsafeBufferPointer(start: eventIds, count: eventCount) - let events = (0.. Bool { + if stream != nil { + return false + } + var context = FSEventStreamContext() + context.info = Unmanaged.passUnretained(self).toOpaque() + guard + let stream = FSEventStreamCreate( + nil, + { + _, clientCallBackInfo, eventCount, eventPaths, eventFlags, eventIds in + let helper = Unmanaged.fromOpaque(clientCallBackInfo!) + .takeUnretainedValue() + let pathsBase = eventPaths.assumingMemoryBound(to: UnsafePointer.self) + let pathsPtr = UnsafeBufferPointer(start: pathsBase, count: eventCount) + let flagsPtr = UnsafeBufferPointer(start: eventFlags, count: eventCount) + let eventIDsPtr = UnsafeBufferPointer(start: eventIds, count: eventCount) + let events = (0.. vecEntry; - while (!zfdContentConsolidatorIncomingStream.eof()) - { // Xcode 13 能用的 ObjCpp 與 Cpp 並無原生支援「\h」這個 Regex 參數的能力,只能逐行處理。 - string zfdBuffer; - getline(zfdContentConsolidatorIncomingStream, zfdBuffer); - vecEntry.push_back(zfdBuffer); - } - // 第一遍 for 用來統整每行內的內容。 - // regex sedCJKWhiteSpace("\\x{3000}"), sedNonBreakWhiteSpace("\\x{A0}"), sedWhiteSpace("\\s+"), - // sedLeadingSpace("^\\s"), sedTrailingSpace("\\s$"); // 這樣寫會導致輸入法敲不了任何字,推測 Xcode 13 支援的 cpp / - // objCpp 可能對某些 Regex 寫法有相容性問題。 regex sedCJKWhiteSpace(" "), sedNonBreakWhiteSpace(" "), - // sedWhiteSpace("\\s+"), sedLeadingSpace("^\\s"), sedTrailingSpace("\\s$"); // RegEx 先定義好。 - regex sedToConsolidate("( +| +| +|\t+)+"), sedToTrim("(^\\s|\\s$)"); - for (int i = 0; i < vecEntry.size(); i++) - { // 第一遍 for 用來統整每行內的內容。 - if (vecEntry[i].size() != 0) - { // 不要理會空行,否則給空行加上 endl 等於再加空行。 - // RegEx 處理順序:先將全形空格換成西文空格,然後合併任何意義上的連續空格(包括 tab - // 等),最後去除每行首尾空格。 vecEntry[i] = regex_replace(vecEntry[i], sedCJKWhiteSpace, " ").c_str(); // - // 中日韓全形空格轉為 ASCII 空格。 vecEntry[i] = regex_replace(vecEntry[i], sedNonBreakWhiteSpace, " - // ").c_str(); // Non-Break 型空格轉為 ASCII 空格。 vecEntry[i] = regex_replace(vecEntry[i], sedWhiteSpace, - // " ").c_str(); // 所有意義上的連續的 \s 型空格都轉為單個 ASCII 空格。 vecEntry[i] = - // regex_replace(vecEntry[i], sedLeadingSpace, "").c_str(); // 去掉行首空格。 vecEntry[i] = - // regex_replace(vecEntry[i], sedTrailingSpace, "").c_str(); // 去掉行尾空格。 - // 上述命令分步驟執行容易產生效能問題,故濃縮為下述兩句。 - vecEntry[i] = regex_replace(vecEntry[i], sedToConsolidate, " ").c_str(); - vecEntry[i] = regex_replace(vecEntry[i], sedToTrim, "").c_str(); - } - } - // 在第二遍 for 運算之前,針對 vecEntry 去除重複條目。 - std::reverse(vecEntry.begin(), vecEntry.end()); // 先首尾顛倒,免得破壞最新的 override 資訊。 - vecEntry.erase(unique(vecEntry.begin(), vecEntry.end()), vecEntry.end()); // 去重複。 - std::reverse(vecEntry.begin(), vecEntry.end()); // 再顛倒回來。 - // 統整完畢。開始將統整過的內容寫入檔案。 - ofstream zfdContentConsolidatorOutput(path); // 這裡是要從頭開始重寫檔案內容,所以不需要「 ios_base::app 」。 - if (!pragmaCheckResult) - { - zfdContentConsolidatorOutput << FORMATTED_PRAGMA_HEADER << endl; // 寫入經過整理處理的 HEADER。 - } - for (int i = 0; i < vecEntry.size(); i++) - { // 第二遍 for 用來寫入統整過的內容。 - if (vecEntry[i].size() != 0) - { // 這句很重要,不然還是會把經過 RegEx 處理後出現的空行搞到檔案裡。 - zfdContentConsolidatorOutput << vecEntry[i] - << endl; // 這裡是必須得加上 endl 的,不然所有行都變成一個整合行。 - } - } - zfdContentConsolidatorOutput.close(); - if (zfdContentConsolidatorOutput.fail()) - { - if (mgrPrefs.isDebugModeEnabled) - syslog(LOG_CONS, - "// REPORT: Failed to write content-consolidated data to the file. Insufficient Privileges?\n"); - if (mgrPrefs.isDebugModeEnabled) - syslog(LOG_CONS, "// DATA FILE: %s", path); - return false; - } - zfdContentConsolidatorIncomingStream.close(); - if (zfdContentConsolidatorIncomingStream.fail()) - { - if (mgrPrefs.isDebugModeEnabled) - syslog(LOG_CONS, "// REPORT: Failed to read lines through the data file for content-consolidation. " - "Insufficient Privileges?\n"); - if (mgrPrefs.isDebugModeEnabled) - syslog(LOG_CONS, "// DATA FILE: %s", path); - return false; - } - return true; -} // END: CONTENT CONSOLIDATOR. - -} // namespace vChewing diff --git a/Source/Modules/IMEModules/IME.swift b/Source/Modules/IMEModules/IME.swift index 91f9604a..fbdf2f4d 100644 --- a/Source/Modules/IMEModules/IME.swift +++ b/Source/Modules/IMEModules/IME.swift @@ -25,320 +25,325 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import Carbon import Cocoa +// The namespace of this input method. +public enum vChewing {} + public class IME: NSObject { - static let arrSupportedLocales = ["en", "zh-Hant", "zh-Hans", "ja"] - static let dlgOpenPath = NSOpenPanel() + static let arrSupportedLocales = ["en", "zh-Hant", "zh-Hans", "ja"] + static let dlgOpenPath = NSOpenPanel() - // MARK: - 開關判定當前應用究竟是? + // MARK: - 開關判定當前應用究竟是? - @objc static var areWeUsingOurOwnPhraseEditor: Bool = false + static var areWeUsingOurOwnPhraseEditor: Bool = false - // MARK: - 自 ctlInputMethod 讀取當前輸入法的簡繁體模式 + // MARK: - 自 ctlInputMethod 讀取當前輸入法的簡繁體模式 - static func getInputMode(isReversed: Bool = false) -> InputMode { - if isReversed { - return (ctlInputMethod.currentKeyHandler.inputMode == InputMode.imeModeCHT) - ? InputMode.imeModeCHS : InputMode.imeModeCHT - } else { - return ctlInputMethod.currentKeyHandler.inputMode - } - } + static func getInputMode(isReversed: Bool = false) -> InputMode { + if isReversed { + return (ctlInputMethod.currentKeyHandler.inputMode == InputMode.imeModeCHT) + ? InputMode.imeModeCHS : InputMode.imeModeCHT + } else { + return ctlInputMethod.currentKeyHandler.inputMode + } + } - // MARK: - Print debug information to the console. + // MARK: - Print debug information to the console. - @objc static func prtDebugIntel(_ strPrint: String) { - if mgrPrefs.isDebugModeEnabled { - NSLog("vChewingErrorCallback: %@", strPrint) - } - } + static func prtDebugIntel(_ strPrint: String) { + if mgrPrefs.isDebugModeEnabled { + NSLog("vChewingErrorCallback: %@", strPrint) + } + } - // MARK: - Tell whether this IME is running with Root privileges. + // MARK: - Tell whether this IME is running with Root privileges. - @objc static var isSudoMode: Bool { - NSUserName() == "root" - } + static var isSudoMode: Bool { + NSUserName() == "root" + } - // MARK: - Initializing Language Models. + // MARK: - Initializing Language Models. - @objc static func initLangModels(userOnly: Bool) { - if !userOnly { - mgrLangModel.loadDataModels() // 這句還是不要砍了。 - } - // mgrLangModel 的 loadUserPhrases 等函數在自動讀取 dataFolderPath 時, - // 如果發現自訂目錄不可用,則會自動抹去自訂目錄設定、改採預設目錄。 - // 所以這裡不需要特別處理。 - mgrLangModel.loadUserPhrases() - mgrLangModel.loadUserPhraseReplacement() - mgrLangModel.loadUserAssociatedPhrases() - } + static func initLangModels(userOnly: Bool) { + DispatchQueue.global(qos: .userInitiated).async { + // mgrLangModel 的 loadUserPhrases 等函數在自動讀取 dataFolderPath 時, + // 如果發現自訂目錄不可用,則會自動抹去自訂目錄設定、改採預設目錄。 + // 所以這裡不需要特別處理。 + mgrLangModel.loadUserAssociatedPhrases() + mgrLangModel.loadUserPhraseReplacement() + mgrLangModel.loadUserPhrases() + } + if !userOnly { + // mgrLangModel.loadDataModels() + } + } - // MARK: - System Dark Mode Status Detector. + // MARK: - System Dark Mode Status Detector. - @objc static func isDarkMode() -> Bool { - if #available(macOS 10.15, *) { - let appearanceDescription = NSApplication.shared.effectiveAppearance.debugDescription - .lowercased() - if appearanceDescription.contains("dark") { - return true - } - } else if #available(macOS 10.14, *) { - if let appleInterfaceStyle = UserDefaults.standard.object(forKey: "AppleInterfaceStyle") - as? String - { - if appleInterfaceStyle.lowercased().contains("dark") { - return true - } - } - } - return false - } + static func isDarkMode() -> Bool { + if #available(macOS 10.15, *) { + let appearanceDescription = NSApplication.shared.effectiveAppearance.debugDescription + .lowercased() + if appearanceDescription.contains("dark") { + return true + } + } else if #available(macOS 10.14, *) { + if let appleInterfaceStyle = UserDefaults.standard.object(forKey: "AppleInterfaceStyle") + as? String + { + if appleInterfaceStyle.lowercased().contains("dark") { + return true + } + } + } + return false + } - // MARK: - Open a phrase data file. + // MARK: - Open a phrase data file. - static func openPhraseFile(userFileAt path: String) { - func checkIfUserFilesExist() -> Bool { - if !mgrLangModel.chkUserLMFilesExist(InputMode.imeModeCHS) - || !mgrLangModel.chkUserLMFilesExist(InputMode.imeModeCHT) - { - let content = String( - format: NSLocalizedString( - "Please check the permission at \"%@\".", comment: "" - ), - mgrLangModel.dataFolderPath(isDefaultFolder: false) - ) - ctlNonModalAlertWindow.shared.show( - title: NSLocalizedString("Unable to create the user phrase file.", comment: ""), - content: content, confirmButtonTitle: NSLocalizedString("OK", comment: ""), - cancelButtonTitle: nil, cancelAsDefault: false, delegate: nil - ) - NSApp.setActivationPolicy(.accessory) - return false - } - return true - } + static func openPhraseFile(userFileAt path: String) { + func checkIfUserFilesExist() -> Bool { + if !mgrLangModel.chkUserLMFilesExist(InputMode.imeModeCHS) + || !mgrLangModel.chkUserLMFilesExist(InputMode.imeModeCHT) + { + let content = String( + format: NSLocalizedString( + "Please check the permission at \"%@\".", comment: "" + ), + mgrLangModel.dataFolderPath(isDefaultFolder: false) + ) + ctlNonModalAlertWindow.shared.show( + title: NSLocalizedString("Unable to create the user phrase file.", comment: ""), + content: content, confirmButtonTitle: NSLocalizedString("OK", comment: ""), + cancelButtonTitle: nil, cancelAsDefault: false, delegate: nil + ) + NSApp.setActivationPolicy(.accessory) + return false + } + return true + } - if !checkIfUserFilesExist() { - return - } - NSWorkspace.shared.openFile(path, withApplication: "vChewingPhraseEditor") - } + if !checkIfUserFilesExist() { + return + } + NSWorkspace.shared.openFile(path, withApplication: "vChewingPhraseEditor") + } - // MARK: - Trash a file if it exists. + // MARK: - Trash a file if it exists. - @discardableResult static func trashTargetIfExists(_ path: String) -> Bool { - do { - if FileManager.default.fileExists(atPath: path) { - // 塞入垃圾桶 - try FileManager.default.trashItem( - at: URL(fileURLWithPath: path), resultingItemURL: nil - ) - } else { - NSLog("Item doesn't exist: \(path)") - } - } catch let error as NSError { - NSLog("Failed from removing this object: \(path) || Error: \(error)") - return false - } - return true - } + @discardableResult static func trashTargetIfExists(_ path: String) -> Bool { + do { + if FileManager.default.fileExists(atPath: path) { + // 塞入垃圾桶 + try FileManager.default.trashItem( + at: URL(fileURLWithPath: path), resultingItemURL: nil + ) + } else { + NSLog("Item doesn't exist: \(path)") + } + } catch let error as NSError { + NSLog("Failed from removing this object: \(path) || Error: \(error)") + return false + } + return true + } - // MARK: - Uninstall the input method. + // MARK: - Uninstall the input method. - @discardableResult static func uninstall(isSudo: Bool = false, selfKill: Bool = true) -> Int32 { - // 輸入法自毀處理。這裡不用「Bundle.main.bundleURL」是為了方便使用者以 sudo 身分來移除被錯誤安裝到系統目錄內的輸入法。 - guard let bundleID = Bundle.main.bundleIdentifier else { - NSLog("Failed to ensure the bundle identifier.") - return -1 - } + @discardableResult static func uninstall(isSudo: Bool = false, selfKill: Bool = true) -> Int32 { + // 輸入法自毀處理。這裡不用「Bundle.main.bundleURL」是為了方便使用者以 sudo 身分來移除被錯誤安裝到系統目錄內的輸入法。 + guard let bundleID = Bundle.main.bundleIdentifier else { + NSLog("Failed to ensure the bundle identifier.") + return -1 + } - let kTargetBin = "vChewing" - let kTargetBundle = "/vChewing.app" - let pathLibrary = - isSudo - ? "/Library" - : FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask)[0].path - let pathIMELibrary = - isSudo - ? "/Library/Input Methods" - : FileManager.default.urls(for: .inputMethodsDirectory, in: .userDomainMask)[0].path - let pathUnitKeyboardLayouts = "/Keyboard Layouts" - let arrKeyLayoutFiles = [ - "/vChewing ETen.keylayout", "/vChewingKeyLayout.bundle", "/vChewing MiTAC.keylayout", - "/vChewing IBM.keylayout", "/vChewing FakeSeigyou.keylayout", - "/vChewing Dachen.keylayout", - ] + let kTargetBin = "vChewing" + let kTargetBundle = "/vChewing.app" + let pathLibrary = + isSudo + ? "/Library" + : FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask)[0].path + let pathIMELibrary = + isSudo + ? "/Library/Input Methods" + : FileManager.default.urls(for: .inputMethodsDirectory, in: .userDomainMask)[0].path + let pathUnitKeyboardLayouts = "/Keyboard Layouts" + let arrKeyLayoutFiles = [ + "/vChewing ETen.keylayout", "/vChewingKeyLayout.bundle", "/vChewing MiTAC.keylayout", + "/vChewing IBM.keylayout", "/vChewing FakeSeigyou.keylayout", + "/vChewing Dachen.keylayout", + ] - // 先移除各種鍵盤佈局。 - for objPath in arrKeyLayoutFiles { - let objFullPath = pathLibrary + pathUnitKeyboardLayouts + objPath - if !IME.trashTargetIfExists(objFullPath) { return -1 } - } - if CommandLine.arguments.count > 2, CommandLine.arguments[2] == "--all", - CommandLine.arguments[1] == "uninstall" - { - // 再處理是否需要移除放在預設使用者資料夾內的檔案的情況。 - // 如果使用者有在輸入法偏好設定內將該目錄改到別的地方(而不是用 symbol link)的話,則不處理。 - // 目前暫時無法應對 symbol link 的情況。 - IME.trashTargetIfExists(mgrLangModel.dataFolderPath(isDefaultFolder: true)) - IME.trashTargetIfExists(pathLibrary + "/Preferences/" + bundleID + ".plist") // 之後移除 App 偏好設定 - } - if !IME.trashTargetIfExists(pathIMELibrary + kTargetBundle) { return -1 } // 最後移除 App 自身 - // 幹掉殘留在記憶體內的執行緒。 - if selfKill { - let killTask = Process() - killTask.launchPath = "/usr/bin/killall" - killTask.arguments = ["-9", kTargetBin] - killTask.launch() - killTask.waitUntilExit() - } - return 0 - } + // 先移除各種鍵盤佈局。 + for objPath in arrKeyLayoutFiles { + let objFullPath = pathLibrary + pathUnitKeyboardLayouts + objPath + if !IME.trashTargetIfExists(objFullPath) { return -1 } + } + if CommandLine.arguments.count > 2, CommandLine.arguments[2] == "--all", + CommandLine.arguments[1] == "uninstall" + { + // 再處理是否需要移除放在預設使用者資料夾內的檔案的情況。 + // 如果使用者有在輸入法偏好設定內將該目錄改到別的地方(而不是用 symbol link)的話,則不處理。 + // 目前暫時無法應對 symbol link 的情況。 + IME.trashTargetIfExists(mgrLangModel.dataFolderPath(isDefaultFolder: true)) + IME.trashTargetIfExists(pathLibrary + "/Preferences/" + bundleID + ".plist") // 之後移除 App 偏好設定 + } + if !IME.trashTargetIfExists(pathIMELibrary + kTargetBundle) { return -1 } // 最後移除 App 自身 + // 幹掉殘留在記憶體內的執行緒。 + if selfKill { + let killTask = Process() + killTask.launchPath = "/usr/bin/killall" + killTask.arguments = ["-9", kTargetBin] + killTask.launch() + killTask.waitUntilExit() + } + return 0 + } - // MARK: - Registering the input method. + // MARK: - Registering the input method. - @discardableResult static func registerInputMethod() -> Int32 { - guard let bundleID = Bundle.main.bundleIdentifier else { - return -1 - } - let bundleUrl = Bundle.main.bundleURL - var maybeInputSource = InputSourceHelper.inputSource(for: bundleID) + @discardableResult static func registerInputMethod() -> Int32 { + guard let bundleID = Bundle.main.bundleIdentifier else { + return -1 + } + let bundleUrl = Bundle.main.bundleURL + var maybeInputSource = InputSourceHelper.inputSource(for: bundleID) - if maybeInputSource == nil { - NSLog("Registering input source \(bundleID) at \(bundleUrl.absoluteString)") - // then register - let status = InputSourceHelper.registerTnputSource(at: bundleUrl) + if maybeInputSource == nil { + NSLog("Registering input source \(bundleID) at \(bundleUrl.absoluteString)") + // then register + let status = InputSourceHelper.registerTnputSource(at: bundleUrl) - if !status { - NSLog( - "Fatal error: Cannot register input source \(bundleID) at \(bundleUrl.absoluteString)." - ) - return -1 - } + if !status { + NSLog( + "Fatal error: Cannot register input source \(bundleID) at \(bundleUrl.absoluteString)." + ) + return -1 + } - maybeInputSource = InputSourceHelper.inputSource(for: bundleID) - } + maybeInputSource = InputSourceHelper.inputSource(for: bundleID) + } - guard let inputSource = maybeInputSource else { - NSLog("Fatal error: Cannot find input source \(bundleID) after registration.") - return -1 - } + guard let inputSource = maybeInputSource else { + NSLog("Fatal error: Cannot find input source \(bundleID) after registration.") + return -1 + } - if !InputSourceHelper.inputSourceEnabled(for: inputSource) { - NSLog("Enabling input source \(bundleID) at \(bundleUrl.absoluteString).") - let status = InputSourceHelper.enable(inputSource: inputSource) - if !status { - NSLog("Fatal error: Cannot enable input source \(bundleID).") - return -1 - } - if !InputSourceHelper.inputSourceEnabled(for: inputSource) { - NSLog("Fatal error: Cannot enable input source \(bundleID).") - return -1 - } - } + if !InputSourceHelper.inputSourceEnabled(for: inputSource) { + NSLog("Enabling input source \(bundleID) at \(bundleUrl.absoluteString).") + let status = InputSourceHelper.enable(inputSource: inputSource) + if !status { + NSLog("Fatal error: Cannot enable input source \(bundleID).") + return -1 + } + if !InputSourceHelper.inputSourceEnabled(for: inputSource) { + NSLog("Fatal error: Cannot enable input source \(bundleID).") + return -1 + } + } - if CommandLine.arguments.count > 2, CommandLine.arguments[2] == "--all" { - let enabled = InputSourceHelper.enableAllInputMode(for: bundleID) - NSLog( - enabled - ? "All input sources enabled for \(bundleID)" - : "Cannot enable all input sources for \(bundleID), but this is ignored") - } - return 0 - } + if CommandLine.arguments.count > 2, CommandLine.arguments[2] == "--all" { + let enabled = InputSourceHelper.enableAllInputMode(for: bundleID) + NSLog( + enabled + ? "All input sources enabled for \(bundleID)" + : "Cannot enable all input sources for \(bundleID), but this is ignored") + } + return 0 + } - // MARK: - 準備枚舉系統內所有的 ASCII 鍵盤佈局 + // MARK: - 準備枚舉系統內所有的 ASCII 鍵盤佈局 - struct CarbonKeyboardLayout { - var strName: String = "" - var strValue: String = "" - } + struct CarbonKeyboardLayout { + var strName: String = "" + var strValue: String = "" + } - static let arrWhitelistedKeyLayoutsASCII: [String] = [ - "com.apple.keylayout.ABC", - "com.apple.keylayout.ABC-AZERTY", - "com.apple.keylayout.ABC-QWERTZ", - "com.apple.keylayout.British", - "com.apple.keylayout.Colemak", - "com.apple.keylayout.Dvorak", - "com.apple.keylayout.Dvorak-Left", - "com.apple.keylayout.DVORAK-QWERTYCMD", - "com.apple.keylayout.Dvorak-Right", - ] - static var arrEnumerateSystemKeyboardLayouts: [IME.CarbonKeyboardLayout] { - // 提前塞入 macOS 內建的兩款動態鍵盤佈局 - var arrKeyLayouts: [IME.CarbonKeyboardLayout] = [] - arrKeyLayouts += [ - IME.CarbonKeyboardLayout( - strName: NSLocalizedString("Apple Chewing - Dachen", comment: ""), - strValue: "com.apple.keylayout.ZhuyinBopomofo" - ), - IME.CarbonKeyboardLayout( - strName: NSLocalizedString("Apple Chewing - Eten Traditional", comment: ""), - strValue: "com.apple.keylayout.ZhuyinEten" - ), - ] + static let arrWhitelistedKeyLayoutsASCII: [String] = [ + "com.apple.keylayout.ABC", + "com.apple.keylayout.ABC-AZERTY", + "com.apple.keylayout.ABC-QWERTZ", + "com.apple.keylayout.British", + "com.apple.keylayout.Colemak", + "com.apple.keylayout.Dvorak", + "com.apple.keylayout.Dvorak-Left", + "com.apple.keylayout.DVORAK-QWERTYCMD", + "com.apple.keylayout.Dvorak-Right", + ] + static var arrEnumerateSystemKeyboardLayouts: [IME.CarbonKeyboardLayout] { + // 提前塞入 macOS 內建的兩款動態鍵盤佈局 + var arrKeyLayouts: [IME.CarbonKeyboardLayout] = [] + arrKeyLayouts += [ + IME.CarbonKeyboardLayout( + strName: NSLocalizedString("Apple Chewing - Dachen", comment: ""), + strValue: "com.apple.keylayout.ZhuyinBopomofo" + ), + IME.CarbonKeyboardLayout( + strName: NSLocalizedString("Apple Chewing - Eten Traditional", comment: ""), + strValue: "com.apple.keylayout.ZhuyinEten" + ), + ] - // 準備枚舉系統內所有的 ASCII 鍵盤佈局 - var arrKeyLayoutsMACV: [IME.CarbonKeyboardLayout] = [] - var arrKeyLayoutsASCII: [IME.CarbonKeyboardLayout] = [] - let list = TISCreateInputSourceList(nil, true).takeRetainedValue() as! [TISInputSource] - for source in list { - if let ptrCategory = TISGetInputSourceProperty(source, kTISPropertyInputSourceCategory) { - let category = Unmanaged.fromOpaque(ptrCategory).takeUnretainedValue() - if category != kTISCategoryKeyboardInputSource { - continue - } - } else { - continue - } + // 準備枚舉系統內所有的 ASCII 鍵盤佈局 + var arrKeyLayoutsMACV: [IME.CarbonKeyboardLayout] = [] + var arrKeyLayoutsASCII: [IME.CarbonKeyboardLayout] = [] + let list = TISCreateInputSourceList(nil, true).takeRetainedValue() as! [TISInputSource] + for source in list { + if let ptrCategory = TISGetInputSourceProperty(source, kTISPropertyInputSourceCategory) { + let category = Unmanaged.fromOpaque(ptrCategory).takeUnretainedValue() + if category != kTISCategoryKeyboardInputSource { + continue + } + } else { + continue + } - if let ptrASCIICapable = TISGetInputSourceProperty( - source, kTISPropertyInputSourceIsASCIICapable - ) { - let asciiCapable = Unmanaged.fromOpaque(ptrASCIICapable) - .takeUnretainedValue() - if asciiCapable != kCFBooleanTrue { - continue - } - } else { - continue - } + if let ptrASCIICapable = TISGetInputSourceProperty( + source, kTISPropertyInputSourceIsASCIICapable + ) { + let asciiCapable = Unmanaged.fromOpaque(ptrASCIICapable) + .takeUnretainedValue() + if asciiCapable != kCFBooleanTrue { + continue + } + } else { + continue + } - if let ptrSourceType = TISGetInputSourceProperty(source, kTISPropertyInputSourceType) { - let sourceType = Unmanaged.fromOpaque(ptrSourceType).takeUnretainedValue() - if sourceType != kTISTypeKeyboardLayout { - continue - } - } else { - continue - } + if let ptrSourceType = TISGetInputSourceProperty(source, kTISPropertyInputSourceType) { + let sourceType = Unmanaged.fromOpaque(ptrSourceType).takeUnretainedValue() + if sourceType != kTISTypeKeyboardLayout { + continue + } + } else { + continue + } - guard let ptrSourceID = TISGetInputSourceProperty(source, kTISPropertyInputSourceID), - let localizedNamePtr = TISGetInputSourceProperty(source, kTISPropertyLocalizedName) - else { - continue - } + guard let ptrSourceID = TISGetInputSourceProperty(source, kTISPropertyInputSourceID), + let localizedNamePtr = TISGetInputSourceProperty(source, kTISPropertyLocalizedName) + else { + continue + } - let sourceID = String(Unmanaged.fromOpaque(ptrSourceID).takeUnretainedValue()) - let localizedName = String( - Unmanaged.fromOpaque(localizedNamePtr).takeUnretainedValue()) + let sourceID = String(Unmanaged.fromOpaque(ptrSourceID).takeUnretainedValue()) + let localizedName = String( + Unmanaged.fromOpaque(localizedNamePtr).takeUnretainedValue()) - if sourceID.contains("vChewing") { - arrKeyLayoutsMACV += [ - IME.CarbonKeyboardLayout(strName: localizedName, strValue: sourceID) - ] - } + if sourceID.contains("vChewing") { + arrKeyLayoutsMACV += [ + IME.CarbonKeyboardLayout(strName: localizedName, strValue: sourceID) + ] + } - if IME.arrWhitelistedKeyLayoutsASCII.contains(sourceID) { - arrKeyLayoutsASCII += [ - IME.CarbonKeyboardLayout(strName: localizedName, strValue: sourceID) - ] - } - } - arrKeyLayouts += arrKeyLayoutsMACV - arrKeyLayouts += arrKeyLayoutsASCII - return arrKeyLayouts - } + if IME.arrWhitelistedKeyLayoutsASCII.contains(sourceID) { + arrKeyLayoutsASCII += [ + IME.CarbonKeyboardLayout(strName: localizedName, strValue: sourceID) + ] + } + } + arrKeyLayouts += arrKeyLayoutsMACV + arrKeyLayouts += arrKeyLayoutsASCII + return arrKeyLayouts + } } // MARK: - Root Extensions @@ -346,27 +351,27 @@ public class IME: NSObject { // Extend the RangeReplaceableCollection to allow it clean duplicated characters. // Ref: https://stackoverflow.com/questions/25738817/ extension RangeReplaceableCollection where Element: Hashable { - var charDeDuplicate: Self { - var set = Set() - return filter { set.insert($0).inserted } - } + var charDeDuplicate: Self { + var set = Set() + return filter { set.insert($0).inserted } + } } // MARK: - Error Extension extension String: Error {} extension String: LocalizedError { - public var errorDescription: String? { - self - } + public var errorDescription: String? { + self + } } // MARK: - Ensuring trailing slash of a string: extension String { - mutating func ensureTrailingSlash() { - if !hasSuffix("/") { - self += "/" - } - } + mutating func ensureTrailingSlash() { + if !hasSuffix("/") { + self += "/" + } + } } diff --git a/Source/Modules/IMEModules/InputSourceHelper.swift b/Source/Modules/IMEModules/InputSourceHelper.swift index d2f6c98b..8dc5b045 100644 --- a/Source/Modules/IMEModules/InputSourceHelper.swift +++ b/Source/Modules/IMEModules/InputSourceHelper.swift @@ -28,109 +28,101 @@ import Carbon import Cocoa public class InputSourceHelper: NSObject { - @available(*, unavailable) - override public init() { - super.init() - } + @available(*, unavailable) + override public init() { + super.init() + } - public static func allInstalledInputSources() -> [TISInputSource] { - TISCreateInputSourceList(nil, true).takeRetainedValue() as! [TISInputSource] - } + public static func allInstalledInputSources() -> [TISInputSource] { + TISCreateInputSourceList(nil, true).takeRetainedValue() as! [TISInputSource] + } - @objc(inputSourceForProperty:stringValue:) - public static func inputSource(for propertyKey: CFString, stringValue: String) - -> TISInputSource? - { - let stringID = CFStringGetTypeID() - for source in allInstalledInputSources() { - if let propertyPtr = TISGetInputSourceProperty(source, propertyKey) { - let property = Unmanaged.fromOpaque(propertyPtr).takeUnretainedValue() - let typeID = CFGetTypeID(property) - if typeID != stringID { - continue - } - if stringValue == property as? String { - return source - } - } - } - return nil - } + public static func inputSource(for propertyKey: CFString, stringValue: String) + -> TISInputSource? + { + let stringID = CFStringGetTypeID() + for source in allInstalledInputSources() { + if let propertyPtr = TISGetInputSourceProperty(source, propertyKey) { + let property = Unmanaged.fromOpaque(propertyPtr).takeUnretainedValue() + let typeID = CFGetTypeID(property) + if typeID != stringID { + continue + } + if stringValue == property as? String { + return source + } + } + } + return nil + } - @objc(inputSourceForInputSourceID:) - public static func inputSource(for sourceID: String) -> TISInputSource? { - inputSource(for: kTISPropertyInputSourceID, stringValue: sourceID) - } + public static func inputSource(for sourceID: String) -> TISInputSource? { + inputSource(for: kTISPropertyInputSourceID, stringValue: sourceID) + } - @objc(inputSourceEnabled:) - public static func inputSourceEnabled(for source: TISInputSource) -> Bool { - if let valuePts = TISGetInputSourceProperty(source, kTISPropertyInputSourceIsEnabled) { - let value = Unmanaged.fromOpaque(valuePts).takeUnretainedValue() - return value == kCFBooleanTrue - } - return false - } + public static func inputSourceEnabled(for source: TISInputSource) -> Bool { + if let valuePts = TISGetInputSourceProperty(source, kTISPropertyInputSourceIsEnabled) { + let value = Unmanaged.fromOpaque(valuePts).takeUnretainedValue() + return value == kCFBooleanTrue + } + return false + } - @objc(enableInputSource:) - public static func enable(inputSource: TISInputSource) -> Bool { - let status = TISEnableInputSource(inputSource) - return status == noErr - } + public static func enable(inputSource: TISInputSource) -> Bool { + let status = TISEnableInputSource(inputSource) + return status == noErr + } - @objc(enableAllInputModesForInputSourceBundleID:) - public static func enableAllInputMode(for inputSourceBundleD: String) -> Bool { - var enabled = false - for source in allInstalledInputSources() { - guard let bundleIDPtr = TISGetInputSourceProperty(source, kTISPropertyBundleID), - let _ = TISGetInputSourceProperty(source, kTISPropertyInputModeID) - else { - continue - } - let bundleID = Unmanaged.fromOpaque(bundleIDPtr).takeUnretainedValue() - if String(bundleID) == inputSourceBundleD { - let modeEnabled = enable(inputSource: source) - if !modeEnabled { - return false - } - enabled = true - } - } + public static func enableAllInputMode(for inputSourceBundleD: String) -> Bool { + var enabled = false + for source in allInstalledInputSources() { + guard let bundleIDPtr = TISGetInputSourceProperty(source, kTISPropertyBundleID), + let _ = TISGetInputSourceProperty(source, kTISPropertyInputModeID) + else { + continue + } + let bundleID = Unmanaged.fromOpaque(bundleIDPtr).takeUnretainedValue() + if String(bundleID) == inputSourceBundleD { + let modeEnabled = enable(inputSource: source) + if !modeEnabled { + return false + } + enabled = true + } + } - return enabled - } + return enabled + } - @objc(enableInputMode:forInputSourceBundleID:) - public static func enable(inputMode modeID: String, for bundleID: String) -> Bool { - for source in allInstalledInputSources() { - guard let bundleIDPtr = TISGetInputSourceProperty(source, kTISPropertyBundleID), - let modePtr = TISGetInputSourceProperty(source, kTISPropertyInputModeID) - else { - continue - } - let inputsSourceBundleID = Unmanaged.fromOpaque(bundleIDPtr) - .takeUnretainedValue() - let inputsSourceModeID = Unmanaged.fromOpaque(modePtr).takeUnretainedValue() - if modeID == String(inputsSourceModeID), bundleID == String(inputsSourceBundleID) { - let enabled = enable(inputSource: source) - print( - "Attempt to enable input source of mode: \(modeID), bundle ID: \(bundleID), result: \(enabled)" - ) - return enabled - } - } - print("Failed to find any matching input source of mode: \(modeID), bundle ID: \(bundleID)") - return false - } + public static func enable(inputMode modeID: String, for bundleID: String) -> Bool { + for source in allInstalledInputSources() { + guard let bundleIDPtr = TISGetInputSourceProperty(source, kTISPropertyBundleID), + let modePtr = TISGetInputSourceProperty(source, kTISPropertyInputModeID) + else { + continue + } + let inputsSourceBundleID = Unmanaged.fromOpaque(bundleIDPtr) + .takeUnretainedValue() + let inputsSourceModeID = Unmanaged.fromOpaque(modePtr).takeUnretainedValue() + if modeID == String(inputsSourceModeID), bundleID == String(inputsSourceBundleID) { + let enabled = enable(inputSource: source) + print( + "Attempt to enable input source of mode: \(modeID), bundle ID: \(bundleID), result: \(enabled)" + ) + return enabled + } + } + print("Failed to find any matching input source of mode: \(modeID), bundle ID: \(bundleID)") + return false + } - @objc(disableInputSource:) - public static func disable(inputSource: TISInputSource) -> Bool { - let status = TISDisableInputSource(inputSource) - return status == noErr - } + public static func disable(inputSource: TISInputSource) -> Bool { + let status = TISDisableInputSource(inputSource) + return status == noErr + } - @objc(registerInputSource:) - public static func registerTnputSource(at url: URL) -> Bool { - let status = TISRegisterInputSource(url as CFURL) - return status == noErr - } + public static func registerTnputSource(at url: URL) -> Bool { + let status = TISRegisterInputSource(url as CFURL) + return status == noErr + } } diff --git a/Source/Modules/IMEModules/apiUpdate.swift b/Source/Modules/IMEModules/apiUpdate.swift index d4d0b4a0..46bb4f6f 100644 --- a/Source/Modules/IMEModules/apiUpdate.swift +++ b/Source/Modules/IMEModules/apiUpdate.swift @@ -27,154 +27,154 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import Cocoa struct VersionUpdateReport { - var siteUrl: URL? - var currentShortVersion: String = "" - var currentVersion: String = "" - var remoteShortVersion: String = "" - var remoteVersion: String = "" - var versionDescription: String = "" + var siteUrl: URL? + var currentShortVersion: String = "" + var currentVersion: String = "" + var remoteShortVersion: String = "" + var remoteVersion: String = "" + var versionDescription: String = "" } enum VersionUpdateApiResult { - case shouldUpdate(report: VersionUpdateReport) - case noNeedToUpdate - case ignored + case shouldUpdate(report: VersionUpdateReport) + case noNeedToUpdate + case ignored } enum VersionUpdateApiError: Error, LocalizedError { - case connectionError(message: String) + case connectionError(message: String) - var errorDescription: String? { - switch self { - case .connectionError(let message): - return String( - format: NSLocalizedString( - "There may be no internet connection or the server failed to respond.\n\nError message: %@", - comment: "" - ), message - ) - } - } + var errorDescription: String? { + switch self { + case .connectionError(let message): + return String( + format: NSLocalizedString( + "There may be no internet connection or the server failed to respond.\n\nError message: %@", + comment: "" + ), message + ) + } + } } enum VersionUpdateApi { - static let kCheckUpdateAutomatically = "CheckUpdateAutomatically" - static let kNextUpdateCheckDateKey = "NextUpdateCheckDate" - static let kUpdateInfoEndpointKey = "UpdateInfoEndpoint" - static let kUpdateInfoSiteKey = "UpdateInfoSite" - static let kVersionDescription = "VersionDescription" - static let kNextCheckInterval: TimeInterval = 86400.0 - static let kTimeoutInterval: TimeInterval = 60.0 - static func check( - forced: Bool, callback: @escaping (Result) -> Void - ) -> URLSessionTask? { - guard let infoDict = Bundle.main.infoDictionary, - let updateInfoURLString = infoDict[kUpdateInfoEndpointKey] as? String, - let updateInfoURL = URL(string: updateInfoURLString) - else { - return nil - } + static let kCheckUpdateAutomatically = "CheckUpdateAutomatically" + static let kNextUpdateCheckDateKey = "NextUpdateCheckDate" + static let kUpdateInfoEndpointKey = "UpdateInfoEndpoint" + static let kUpdateInfoSiteKey = "UpdateInfoSite" + static let kVersionDescription = "VersionDescription" + static let kNextCheckInterval: TimeInterval = 86400.0 + static let kTimeoutInterval: TimeInterval = 60.0 + static func check( + forced: Bool, callback: @escaping (Result) -> Void + ) -> URLSessionTask? { + guard let infoDict = Bundle.main.infoDictionary, + let updateInfoURLString = infoDict[kUpdateInfoEndpointKey] as? String, + let updateInfoURL = URL(string: updateInfoURLString) + else { + return nil + } - let request = URLRequest( - url: updateInfoURL, cachePolicy: .reloadIgnoringLocalCacheData, - timeoutInterval: kTimeoutInterval - ) - let task = URLSession.shared.dataTask(with: request) { data, _, error in - if let error = error { - DispatchQueue.main.async { - forced - ? callback( - .failure( - VersionUpdateApiError.connectionError( - message: error.localizedDescription))) - : callback(.success(.ignored)) - } - return - } + let request = URLRequest( + url: updateInfoURL, cachePolicy: .reloadIgnoringLocalCacheData, + timeoutInterval: kTimeoutInterval + ) + let task = URLSession.shared.dataTask(with: request) { data, _, error in + if let error = error { + DispatchQueue.main.async { + forced + ? callback( + .failure( + VersionUpdateApiError.connectionError( + message: error.localizedDescription))) + : callback(.success(.ignored)) + } + return + } - do { - guard - let plist = try PropertyListSerialization.propertyList( - from: data ?? Data(), options: [], format: nil - ) as? [AnyHashable: Any], - let remoteVersion = plist[kCFBundleVersionKey] as? String, - let infoDict = Bundle.main.infoDictionary - else { - DispatchQueue.main.async { - forced - ? callback(.success(.noNeedToUpdate)) - : callback(.success(.ignored)) - } - return - } + do { + guard + let plist = try PropertyListSerialization.propertyList( + from: data ?? Data(), options: [], format: nil + ) as? [AnyHashable: Any], + let remoteVersion = plist[kCFBundleVersionKey] as? String, + let infoDict = Bundle.main.infoDictionary + else { + DispatchQueue.main.async { + forced + ? callback(.success(.noNeedToUpdate)) + : callback(.success(.ignored)) + } + return + } - // TODO: Validate info (e.g. bundle identifier) - // TODO: Use HTML to display change log, need a new key like UpdateInfoChangeLogURL for this + // TODO: Validate info (e.g. bundle identifier) + // TODO: Use HTML to display change log, need a new key like UpdateInfoChangeLogURL for this - let currentVersion = infoDict[kCFBundleVersionKey as String] as? String ?? "" - let result = currentVersion.compare( - remoteVersion, options: .numeric, range: nil, locale: nil - ) + let currentVersion = infoDict[kCFBundleVersionKey as String] as? String ?? "" + let result = currentVersion.compare( + remoteVersion, options: .numeric, range: nil, locale: nil + ) - if result != .orderedAscending { - DispatchQueue.main.async { - forced - ? callback(.success(.noNeedToUpdate)) - : callback(.success(.ignored)) - } - IME.prtDebugIntel( - "vChewingDebug: Update // Order is not Ascending, assuming that there's no new version available." - ) - return - } - IME.prtDebugIntel( - "vChewingDebug: Update // New version detected, proceeding to the next phase.") - guard let siteInfoURLString = plist[kUpdateInfoSiteKey] as? String, - let siteInfoURL = URL(string: siteInfoURLString) - else { - DispatchQueue.main.async { - forced - ? callback(.success(.noNeedToUpdate)) - : callback(.success(.ignored)) - } - IME.prtDebugIntel( - "vChewingDebug: Update // Failed from retrieving / parsing URL intel.") - return - } - IME.prtDebugIntel( - "vChewingDebug: Update // URL intel retrieved, proceeding to the next phase.") - var report = VersionUpdateReport(siteUrl: siteInfoURL) - var versionDescription = "" - let versionDescriptions = plist[kVersionDescription] as? [AnyHashable: Any] - if let versionDescriptions = versionDescriptions { - var locale = "en" - let preferredTags = Bundle.preferredLocalizations(from: IME.arrSupportedLocales) - if let first = preferredTags.first { - locale = first - } - versionDescription = - versionDescriptions[locale] as? String ?? versionDescriptions["en"] - as? String ?? "" - if !versionDescription.isEmpty { - versionDescription = "\n\n" + versionDescription - } - } - report.currentShortVersion = infoDict["CFBundleShortVersionString"] as? String ?? "" - report.currentVersion = currentVersion - report.remoteShortVersion = plist["CFBundleShortVersionString"] as? String ?? "" - report.remoteVersion = remoteVersion - report.versionDescription = versionDescription - DispatchQueue.main.async { - callback(.success(.shouldUpdate(report: report))) - } - IME.prtDebugIntel("vChewingDebug: Update // Callbck Complete.") - } catch { - DispatchQueue.main.async { - forced ? callback(.success(.noNeedToUpdate)) : callback(.success(.ignored)) - } - } - } - task.resume() - return task - } + if result != .orderedAscending { + DispatchQueue.main.async { + forced + ? callback(.success(.noNeedToUpdate)) + : callback(.success(.ignored)) + } + IME.prtDebugIntel( + "vChewingDebug: Update // Order is not Ascending, assuming that there's no new version available." + ) + return + } + IME.prtDebugIntel( + "vChewingDebug: Update // New version detected, proceeding to the next phase.") + guard let siteInfoURLString = plist[kUpdateInfoSiteKey] as? String, + let siteInfoURL = URL(string: siteInfoURLString) + else { + DispatchQueue.main.async { + forced + ? callback(.success(.noNeedToUpdate)) + : callback(.success(.ignored)) + } + IME.prtDebugIntel( + "vChewingDebug: Update // Failed from retrieving / parsing URL intel.") + return + } + IME.prtDebugIntel( + "vChewingDebug: Update // URL intel retrieved, proceeding to the next phase.") + var report = VersionUpdateReport(siteUrl: siteInfoURL) + var versionDescription = "" + let versionDescriptions = plist[kVersionDescription] as? [AnyHashable: Any] + if let versionDescriptions = versionDescriptions { + var locale = "en" + let preferredTags = Bundle.preferredLocalizations(from: IME.arrSupportedLocales) + if let first = preferredTags.first { + locale = first + } + versionDescription = + versionDescriptions[locale] as? String ?? versionDescriptions["en"] + as? String ?? "" + if !versionDescription.isEmpty { + versionDescription = "\n\n" + versionDescription + } + } + report.currentShortVersion = infoDict["CFBundleShortVersionString"] as? String ?? "" + report.currentVersion = currentVersion + report.remoteShortVersion = plist["CFBundleShortVersionString"] as? String ?? "" + report.remoteVersion = remoteVersion + report.versionDescription = versionDescription + DispatchQueue.main.async { + callback(.success(.shouldUpdate(report: report))) + } + IME.prtDebugIntel("vChewingDebug: Update // Callbck Complete.") + } catch { + DispatchQueue.main.async { + forced ? callback(.success(.noNeedToUpdate)) : callback(.success(.ignored)) + } + } + } + task.resume() + return task + } } diff --git a/Source/Modules/IMEModules/ctlInputMethod.swift b/Source/Modules/IMEModules/ctlInputMethod.swift index 8bf699bf..6edbbb5b 100644 --- a/Source/Modules/IMEModules/ctlInputMethod.swift +++ b/Source/Modules/IMEModules/ctlInputMethod.swift @@ -32,624 +32,620 @@ private let kMinKeyLabelSize: CGFloat = 10 private var ctlCandidateCurrent: ctlCandidate? extension ctlCandidate { - fileprivate static let horizontal = ctlCandidateHorizontal() - fileprivate static let vertical = ctlCandidateVertical() + fileprivate static let horizontal = ctlCandidateHorizontal() + fileprivate static let vertical = ctlCandidateVertical() } @objc(ctlInputMethod) class ctlInputMethod: IMKInputController { - @objc static let kIMEModeCHS = "org.atelierInmu.inputmethod.vChewing.IMECHS" - @objc static let kIMEModeCHT = "org.atelierInmu.inputmethod.vChewing.IMECHT" - @objc static let kIMEModeNULL = "org.atelierInmu.inputmethod.vChewing.IMENULL" + @objc static var areWeDeleting = false - @objc static var areWeDeleting = false + private static let tooltipController = TooltipController() - private static let tooltipController = TooltipController() + // MARK: - - // MARK: - + private var currentClient: Any? - private var currentClient: Any? + private var keyHandler: KeyHandler = .init() + private var state: InputState = .Empty() - private var keyHandler: KeyHandler = .init() - private var state: InputState = .Empty() + // 想讓 KeyHandler 能夠被外界調查狀態與參數的話,就得對 KeyHandler 做常態處理。 + // 這樣 InputState 可以藉由這個 ctlInputMethod 了解到當前的輸入模式是簡體中文還是繁體中文。 + // 然而,要是直接對 keyHandler 做常態處理的話,反而會導致 InputHandler 無法協同處理。 + // 所以才需要「currentKeyHandler」這個假 KeyHandler。 + // 這個「currentKeyHandler」僅用來讓其他模組知道當前的輸入模式是什麼模式,除此之外別無屌用。 + static var currentKeyHandler: KeyHandler = .init() + @objc static var currentInputMode = mgrPrefs.mostRecentInputMode - // 想讓 KeyHandler 能夠被外界調查狀態與參數的話,就得對 KeyHandler 做常態處理。 - // 這樣 InputState 可以藉由這個 ctlInputMethod 了解到當前的輸入模式是簡體中文還是繁體中文。 - // 然而,要是直接對 keyHandler 做常態處理的話,反而會導致 InputHandler 無法協同處理。 - // 所以才需要「currentKeyHandler」這個假 KeyHandler。 - // 這個「currentKeyHandler」僅用來讓其他模組知道當前的輸入模式是什麼模式,除此之外別無屌用。 - static var currentKeyHandler: KeyHandler = .init() - @objc static var currentInputMode = mgrPrefs.mostRecentInputMode + // MARK: - Keyboard Layout Specifier - // MARK: - Keyboard Layout Specifier + @objc func setKeyLayout() { + if let client = currentClient { + (client as? IMKTextInput)?.overrideKeyboard(withKeyboardNamed: mgrPrefs.basicKeyboardLayout) + } + } - @objc func setKeyLayout() { - if let client = currentClient { - (client as? IMKTextInput)?.overrideKeyboard(withKeyboardNamed: mgrPrefs.basicKeyboardLayout) - } - } + // MARK: - IMKInputController methods - // MARK: - IMKInputController methods + override init!(server: IMKServer!, delegate: Any!, client inputClient: Any!) { + super.init(server: server, delegate: delegate, client: inputClient) + keyHandler.delegate = self + // 下述兩行很有必要,否則輸入法會在手動重啟之後無法立刻生效。 + activateServer(inputClient) + resetKeyHandler() + } - override init!(server: IMKServer!, delegate: Any!, client inputClient: Any!) { - super.init(server: server, delegate: delegate, client: inputClient) - keyHandler.delegate = self - // 下述兩行很有必要,否則輸入法會在手動重啟之後無法立刻生效。 - activateServer(inputClient) - resetKeyHandler() - } + // MARK: - KeyHandler Reset Command - // MARK: - KeyHandler Reset Command + func resetKeyHandler() { + if let currentClient = currentClient { + keyHandler.clear() + handle(state: InputState.Empty(), client: currentClient) + } + } - func resetKeyHandler() { - if let currentClient = currentClient { - keyHandler.clear() - handle(state: InputState.Empty(), client: currentClient) - } - } + // MARK: - IMKStateSetting protocol methods - // MARK: - IMKStateSetting protocol methods + override func activateServer(_ client: Any!) { + UserDefaults.standard.synchronize() - override func activateServer(_ client: Any!) { - UserDefaults.standard.synchronize() + // reset the state + currentClient = client - // reset the state - currentClient = client + keyHandler.clear() + Composer.ensureParser() + if let bundleCheckID = (client as? IMKTextInput)?.bundleIdentifier() { + if bundleCheckID != Bundle.main.bundleIdentifier { + // Override the keyboard layout to the basic one. + setKeyLayout() + handle(state: .Empty(), client: client) + } + } + (NSApp.delegate as? AppDelegate)?.checkForUpdate() + } - keyHandler.clear() - keyHandler.ensurePhoneticParser() - if let bundleCheckID = (client as? IMKTextInput)?.bundleIdentifier() { - if bundleCheckID != Bundle.main.bundleIdentifier { - // Override the keyboard layout to the basic one. - setKeyLayout() - handle(state: .Empty(), client: client) - } - } - (NSApp.delegate as? AppDelegate)?.checkForUpdate() - } + override func deactivateServer(_ client: Any!) { + keyHandler.clear() + currentClient = nil + handle(state: .Empty(), client: client) + handle(state: .Deactivated(), client: client) + } - override func deactivateServer(_ client: Any!) { - keyHandler.clear() - currentClient = nil - handle(state: .Empty(), client: client) - handle(state: .Deactivated(), client: client) - } + override func setValue(_ value: Any!, forTag _: Int, client: Any!) { + var newInputMode = InputMode(rawValue: value as? String ?? "") ?? InputMode.imeModeNULL + switch newInputMode { + case InputMode.imeModeCHS: + newInputMode = InputMode.imeModeCHS + case InputMode.imeModeCHT: + newInputMode = InputMode.imeModeCHT + default: + newInputMode = InputMode.imeModeNULL + } + mgrLangModel.loadDataModel(newInputMode) - override func setValue(_ value: Any!, forTag _: Int, client: Any!) { - var newInputMode = InputMode(rawValue: value as? String ?? InputMode.imeModeNULL.rawValue) - switch newInputMode { - case InputMode.imeModeCHS: - newInputMode = InputMode.imeModeCHS - case InputMode.imeModeCHT: - newInputMode = InputMode.imeModeCHT - default: - newInputMode = InputMode.imeModeNULL - } - mgrLangModel.loadDataModel(newInputMode) + if keyHandler.inputMode != newInputMode { + UserDefaults.standard.synchronize() + keyHandler.clear() + keyHandler.inputMode = newInputMode + if let bundleCheckID = (client as? IMKTextInput)?.bundleIdentifier() { + if bundleCheckID != Bundle.main.bundleIdentifier { + // Remember to override the keyboard layout again -- treat this as an activate event. + setKeyLayout() + handle(state: .Empty(), client: client) + } + } + } - if keyHandler.inputMode != newInputMode { - UserDefaults.standard.synchronize() - keyHandler.clear() - keyHandler.inputMode = newInputMode - if let bundleCheckID = (client as? IMKTextInput)?.bundleIdentifier() { - if bundleCheckID != Bundle.main.bundleIdentifier { - // Remember to override the keyboard layout again -- treat this as an activate event. - setKeyLayout() - handle(state: .Empty(), client: client) - } - } - } + // 讓外界知道目前的簡繁體輸入模式。 + ctlInputMethod.currentKeyHandler.inputMode = keyHandler.inputMode + } - // 讓外界知道目前的簡繁體輸入模式。 - ctlInputMethod.currentKeyHandler.inputMode = keyHandler.inputMode - } + // MARK: - IMKServerInput protocol methods - // MARK: - IMKServerInput protocol methods + override func recognizedEvents(_: Any!) -> Int { + let events: NSEvent.EventTypeMask = [.keyDown, .flagsChanged] + return Int(events.rawValue) + } - override func recognizedEvents(_: Any!) -> Int { - let events: NSEvent.EventTypeMask = [.keyDown, .flagsChanged] - return Int(events.rawValue) - } + override func handle(_ event: NSEvent!, client: Any!) -> Bool { + // 這裡仍舊需要判斷 flags。之前使輸入法狀態卡住無法敲漢字的問題已在 KeyHandler 內修復。 + // 這裡不判斷 flags 的話,用方向鍵前後定位光標之後,再次試圖觸發組字區時、反而會在首次按鍵時失敗。 + // 同時注意:必須在 event.type == .flagsChanged 結尾插入 return false, + // 否則,每次處理這種判斷時都會觸發 NSInternalInconsistencyException。 + if event.type == .flagsChanged { + return false + } - override func handle(_ event: NSEvent!, client: Any!) -> Bool { - // 這裡仍舊需要判斷 flags。之前使輸入法狀態卡住無法敲漢字的問題已在 KeyHandler 內修復。 - // 這裡不判斷 flags 的話,用方向鍵前後定位光標之後,再次試圖觸發組字區時、反而會在首次按鍵時失敗。 - // 同時注意:必須在 event.type == .flagsChanged 結尾插入 return false, - // 否則,每次處理這種判斷時都會觸發 NSInternalInconsistencyException。 - if event.type == .flagsChanged { - return false - } + // 準備修飾鍵,用來判定是否需要利用就地新增語彙時的 Enter 鍵來砍詞。 + ctlInputMethod.areWeDeleting = event.modifierFlags.contains([.shift, .command]) - // 準備修飾鍵,用來判定是否需要利用就地新增語彙時的 Enter 鍵來砍詞。 - ctlInputMethod.areWeDeleting = event.modifierFlags.contains([.shift, .command]) + var textFrame = NSRect.zero + let attributes: [AnyHashable: Any]? = (client as? IMKTextInput)?.attributes( + forCharacterIndex: 0, lineHeightRectangle: &textFrame + ) + let useVerticalMode = + (attributes?["IMKTextOrientation"] as? NSNumber)?.intValue == 0 || false - var textFrame = NSRect.zero - let attributes: [AnyHashable: Any]? = (client as? IMKTextInput)?.attributes( - forCharacterIndex: 0, lineHeightRectangle: &textFrame - ) - let useVerticalMode = - (attributes?["IMKTextOrientation"] as? NSNumber)?.intValue == 0 || false + if (client as? IMKTextInput)?.bundleIdentifier() + == "org.atelierInmu.vChewing.vChewingPhraseEditor" + { + IME.areWeUsingOurOwnPhraseEditor = true + } else { + IME.areWeUsingOurOwnPhraseEditor = false + } - if (client as? IMKTextInput)?.bundleIdentifier() - == "org.atelierInmu.vChewing.vChewingPhraseEditor" - { - IME.areWeUsingOurOwnPhraseEditor = true - } else { - IME.areWeUsingOurOwnPhraseEditor = false - } + let input = InputHandler(event: event, isVerticalMode: useVerticalMode) - let input = InputHandler(event: event, isVerticalMode: useVerticalMode) - - let result = keyHandler.handle(input: input, state: state) { newState in - self.handle(state: newState, client: client) - } errorCallback: { - clsSFX.beep() - } - return result - } + let result = keyHandler.handle(input: input, state: state) { newState in + self.handle(state: newState, client: client) + } errorCallback: { + clsSFX.beep() + } + return result + } } // MARK: - State Handling extension ctlInputMethod { - private func handle(state newState: InputState, client: Any?) { - let previous = state - state = newState + private func handle(state newState: InputState, client: Any?) { + let previous = state + state = newState - if let newState = newState as? InputState.Deactivated { - handle(state: newState, previous: previous, client: client) - } else if let newState = newState as? InputState.Empty { - handle(state: newState, previous: previous, client: client) - } else if let newState = newState as? InputState.EmptyIgnoringPreviousState { - handle(state: newState, previous: previous, client: client) - } else if let newState = newState as? InputState.Committing { - handle(state: newState, previous: previous, client: client) - } else if let newState = newState as? InputState.Inputting { - handle(state: newState, previous: previous, client: client) - } else if let newState = newState as? InputState.Marking { - handle(state: newState, previous: previous, client: client) - } else if let newState = newState as? InputState.ChoosingCandidate { - handle(state: newState, previous: previous, client: client) - } else if let newState = newState as? InputState.AssociatedPhrases { - handle(state: newState, previous: previous, client: client) - } - } + if let newState = newState as? InputState.Deactivated { + handle(state: newState, previous: previous, client: client) + } else if let newState = newState as? InputState.Empty { + handle(state: newState, previous: previous, client: client) + } else if let newState = newState as? InputState.EmptyIgnoringPreviousState { + handle(state: newState, previous: previous, client: client) + } else if let newState = newState as? InputState.Committing { + handle(state: newState, previous: previous, client: client) + } else if let newState = newState as? InputState.Inputting { + handle(state: newState, previous: previous, client: client) + } else if let newState = newState as? InputState.Marking { + handle(state: newState, previous: previous, client: client) + } else if let newState = newState as? InputState.ChoosingCandidate { + handle(state: newState, previous: previous, client: client) + } else if let newState = newState as? InputState.AssociatedPhrases { + handle(state: newState, previous: previous, client: client) + } + } - private func commit(text: String, client: Any!) { - func kanjiConversionIfRequired(_ text: String) -> String { - if keyHandler.inputMode == InputMode.imeModeCHT { - if !mgrPrefs.chineseConversionEnabled, mgrPrefs.shiftJISShinjitaiOutputEnabled { - return vChewingKanjiConverter.cnvTradToJIS(text) - } - if mgrPrefs.chineseConversionEnabled, !mgrPrefs.shiftJISShinjitaiOutputEnabled { - return vChewingKanjiConverter.cnvTradToKangXi(text) - } - // 本來這兩個開關不該同時開啟的,但萬一被開啟了的話就這樣處理: - if mgrPrefs.chineseConversionEnabled, mgrPrefs.shiftJISShinjitaiOutputEnabled { - return vChewingKanjiConverter.cnvTradToJIS(text) - } - // if (!mgrPrefs.chineseConversionEnabled && !mgrPrefs.shiftJISShinjitaiOutputEnabled) || (keyHandler.inputMode != InputMode.imeModeCHT); - return text - } - return text - } + private func commit(text: String, client: Any!) { + func kanjiConversionIfRequired(_ text: String) -> String { + if keyHandler.inputMode == InputMode.imeModeCHT { + if !mgrPrefs.chineseConversionEnabled, mgrPrefs.shiftJISShinjitaiOutputEnabled { + return vChewingKanjiConverter.cnvTradToJIS(text) + } + if mgrPrefs.chineseConversionEnabled, !mgrPrefs.shiftJISShinjitaiOutputEnabled { + return vChewingKanjiConverter.cnvTradToKangXi(text) + } + // 本來這兩個開關不該同時開啟的,但萬一被開啟了的話就這樣處理: + if mgrPrefs.chineseConversionEnabled, mgrPrefs.shiftJISShinjitaiOutputEnabled { + return vChewingKanjiConverter.cnvTradToJIS(text) + } + // if (!mgrPrefs.chineseConversionEnabled && !mgrPrefs.shiftJISShinjitaiOutputEnabled) || (keyHandler.inputMode != InputMode.imeModeCHT); + return text + } + return text + } - let buffer = kanjiConversionIfRequired(text) - if buffer.isEmpty { - return - } - (client as? IMKTextInput)?.insertText( - buffer, replacementRange: NSRange(location: NSNotFound, length: NSNotFound) - ) - } + let buffer = kanjiConversionIfRequired(text) + if buffer.isEmpty { + return + } + (client as? IMKTextInput)?.insertText( + buffer, replacementRange: NSRange(location: NSNotFound, length: NSNotFound) + ) + } - private func handle(state _: InputState.Deactivated, previous: InputState, client: Any?) { - currentClient = nil + private func handle(state _: InputState.Deactivated, previous: InputState, client: Any?) { + currentClient = nil - ctlCandidateCurrent?.delegate = nil - ctlCandidateCurrent?.visible = false - hideTooltip() + ctlCandidateCurrent?.delegate = nil + ctlCandidateCurrent?.visible = false + hideTooltip() - if let previous = previous as? InputState.NotEmpty { - commit(text: previous.composingBuffer, client: client) - } - (client as? IMKTextInput)?.setMarkedText( - "", selectionRange: NSRange(location: 0, length: 0), - replacementRange: NSRange(location: NSNotFound, length: NSNotFound) - ) - } + if let previous = previous as? InputState.NotEmpty { + commit(text: previous.composingBuffer, client: client) + } + (client as? IMKTextInput)?.setMarkedText( + "", selectionRange: NSRange(location: 0, length: 0), + replacementRange: NSRange(location: NSNotFound, length: NSNotFound) + ) + } - private func handle(state _: InputState.Empty, previous: InputState, client: Any?) { - ctlCandidateCurrent?.visible = false - hideTooltip() + private func handle(state _: InputState.Empty, previous: InputState, client: Any?) { + ctlCandidateCurrent?.visible = false + hideTooltip() - guard let client = client as? IMKTextInput else { - return - } + guard let client = client as? IMKTextInput else { + return + } - if let previous = previous as? InputState.NotEmpty { - commit(text: previous.composingBuffer, client: client) - } - client.setMarkedText( - "", selectionRange: NSRange(location: 0, length: 0), - replacementRange: NSRange(location: NSNotFound, length: NSNotFound) - ) - } + if let previous = previous as? InputState.NotEmpty { + commit(text: previous.composingBuffer, client: client) + } + client.setMarkedText( + "", selectionRange: NSRange(location: 0, length: 0), + replacementRange: NSRange(location: NSNotFound, length: NSNotFound) + ) + } - private func handle( - state _: InputState.EmptyIgnoringPreviousState, previous _: InputState, client: Any! - ) { - ctlCandidateCurrent?.visible = false - hideTooltip() + private func handle( + state _: InputState.EmptyIgnoringPreviousState, previous _: InputState, client: Any! + ) { + ctlCandidateCurrent?.visible = false + hideTooltip() - guard let client = client as? IMKTextInput else { - return - } + guard let client = client as? IMKTextInput else { + return + } - client.setMarkedText( - "", selectionRange: NSRange(location: 0, length: 0), - replacementRange: NSRange(location: NSNotFound, length: NSNotFound) - ) - } + client.setMarkedText( + "", selectionRange: NSRange(location: 0, length: 0), + replacementRange: NSRange(location: NSNotFound, length: NSNotFound) + ) + } - private func handle(state: InputState.Committing, previous _: InputState, client: Any?) { - ctlCandidateCurrent?.visible = false - hideTooltip() + private func handle(state: InputState.Committing, previous _: InputState, client: Any?) { + ctlCandidateCurrent?.visible = false + hideTooltip() - guard let client = client as? IMKTextInput else { - return - } + guard let client = client as? IMKTextInput else { + return + } - let poppedText = state.poppedText - if !poppedText.isEmpty { - commit(text: poppedText, client: client) - } - client.setMarkedText( - "", selectionRange: NSRange(location: 0, length: 0), - replacementRange: NSRange(location: NSNotFound, length: NSNotFound) - ) - } + let poppedText = state.poppedText + if !poppedText.isEmpty { + commit(text: poppedText, client: client) + } + client.setMarkedText( + "", selectionRange: NSRange(location: 0, length: 0), + replacementRange: NSRange(location: NSNotFound, length: NSNotFound) + ) + } - private func handle(state: InputState.Inputting, previous _: InputState, client: Any?) { - ctlCandidateCurrent?.visible = false - hideTooltip() + private func handle(state: InputState.Inputting, previous _: InputState, client: Any?) { + ctlCandidateCurrent?.visible = false + hideTooltip() - guard let client = client as? IMKTextInput else { - return - } + guard let client = client as? IMKTextInput else { + return + } - let poppedText = state.poppedText - if !poppedText.isEmpty { - commit(text: poppedText, client: client) - } + let poppedText = state.poppedText + if !poppedText.isEmpty { + commit(text: poppedText, client: client) + } - // the selection range is where the cursor is, with the length being 0 and replacement range NSNotFound, - // i.e. the client app needs to take care of where to put this composing buffer - client.setMarkedText( - state.attributedString, selectionRange: NSRange(location: Int(state.cursorIndex), length: 0), - replacementRange: NSRange(location: NSNotFound, length: NSNotFound) - ) - if !state.tooltip.isEmpty { - show( - tooltip: state.tooltip, composingBuffer: state.composingBuffer, - cursorIndex: state.cursorIndex, client: client - ) - } - } + // the selection range is where the cursor is, with the length being 0 and replacement range NSNotFound, + // i.e. the client app needs to take care of where to put this composing buffer + client.setMarkedText( + state.attributedString, selectionRange: NSRange(location: Int(state.cursorIndex), length: 0), + replacementRange: NSRange(location: NSNotFound, length: NSNotFound) + ) + if !state.tooltip.isEmpty { + show( + tooltip: state.tooltip, composingBuffer: state.composingBuffer, + cursorIndex: state.cursorIndex, client: client + ) + } + } - private func handle(state: InputState.Marking, previous _: InputState, client: Any?) { - ctlCandidateCurrent?.visible = false - guard let client = client as? IMKTextInput else { - hideTooltip() - return - } + private func handle(state: InputState.Marking, previous _: InputState, client: Any?) { + ctlCandidateCurrent?.visible = false + guard let client = client as? IMKTextInput else { + hideTooltip() + return + } - // the selection range is where the cursor is, with the length being 0 and replacement range NSNotFound, - // i.e. the client app needs to take care of where to put this composing buffer - client.setMarkedText( - state.attributedString, selectionRange: NSRange(location: Int(state.cursorIndex), length: 0), - replacementRange: NSRange(location: NSNotFound, length: NSNotFound) - ) + // the selection range is where the cursor is, with the length being 0 and replacement range NSNotFound, + // i.e. the client app needs to take care of where to put this composing buffer + client.setMarkedText( + state.attributedString, selectionRange: NSRange(location: Int(state.cursorIndex), length: 0), + replacementRange: NSRange(location: NSNotFound, length: NSNotFound) + ) - if state.tooltip.isEmpty { - hideTooltip() - } else { - show( - tooltip: state.tooltip, composingBuffer: state.composingBuffer, - cursorIndex: state.markerIndex, client: client - ) - } - } + if state.tooltip.isEmpty { + hideTooltip() + } else { + show( + tooltip: state.tooltip, composingBuffer: state.composingBuffer, + cursorIndex: state.markerIndex, client: client + ) + } + } - private func handle(state: InputState.ChoosingCandidate, previous _: InputState, client: Any?) { - hideTooltip() - guard let client = client as? IMKTextInput else { - ctlCandidateCurrent?.visible = false - return - } + private func handle(state: InputState.ChoosingCandidate, previous _: InputState, client: Any?) { + hideTooltip() + guard let client = client as? IMKTextInput else { + ctlCandidateCurrent?.visible = false + return + } - // the selection range is where the cursor is, with the length being 0 and replacement range NSNotFound, - // i.e. the client app needs to take care of where to put this composing buffer - client.setMarkedText( - state.attributedString, selectionRange: NSRange(location: Int(state.cursorIndex), length: 0), - replacementRange: NSRange(location: NSNotFound, length: NSNotFound) - ) - show(candidateWindowWith: state, client: client) - } + // the selection range is where the cursor is, with the length being 0 and replacement range NSNotFound, + // i.e. the client app needs to take care of where to put this composing buffer + client.setMarkedText( + state.attributedString, selectionRange: NSRange(location: Int(state.cursorIndex), length: 0), + replacementRange: NSRange(location: NSNotFound, length: NSNotFound) + ) + show(candidateWindowWith: state, client: client) + } - private func handle(state: InputState.AssociatedPhrases, previous _: InputState, client: Any?) { - hideTooltip() - guard let client = client as? IMKTextInput else { - ctlCandidateCurrent?.visible = false - return - } - client.setMarkedText( - "", selectionRange: NSRange(location: 0, length: 0), - replacementRange: NSRange(location: NSNotFound, length: NSNotFound) - ) - show(candidateWindowWith: state, client: client) - } + private func handle(state: InputState.AssociatedPhrases, previous _: InputState, client: Any?) { + hideTooltip() + guard let client = client as? IMKTextInput else { + ctlCandidateCurrent?.visible = false + return + } + client.setMarkedText( + "", selectionRange: NSRange(location: 0, length: 0), + replacementRange: NSRange(location: NSNotFound, length: NSNotFound) + ) + show(candidateWindowWith: state, client: client) + } } // MARK: - extension ctlInputMethod { - private func show(candidateWindowWith state: InputState, client: Any!) { - let useVerticalMode: Bool = { - var useVerticalMode = false - var candidates: [String] = [] - if let state = state as? InputState.ChoosingCandidate { - useVerticalMode = state.useVerticalMode - candidates = state.candidates - } else if let state = state as? InputState.AssociatedPhrases { - useVerticalMode = state.useVerticalMode - candidates = state.candidates - } - if useVerticalMode == true { - return true - } - candidates.sort { - $0.count > $1.count - } - // If there is a candidate which is too long, we use the vertical - // candidate list window automatically. - if candidates.first?.count ?? 0 > 8 { - // return true // 禁用這一項。威注音回頭會換候選窗格。 - } - return false - }() + private func show(candidateWindowWith state: InputState, client: Any!) { + let useVerticalMode: Bool = { + var useVerticalMode = false + var candidates: [String] = [] + if let state = state as? InputState.ChoosingCandidate { + useVerticalMode = state.useVerticalMode + candidates = state.candidates + } else if let state = state as? InputState.AssociatedPhrases { + useVerticalMode = state.useVerticalMode + candidates = state.candidates + } + if useVerticalMode == true { + return true + } + candidates.sort { + $0.count > $1.count + } + // If there is a candidate which is too long, we use the vertical + // candidate list window automatically. + if candidates.first?.count ?? 0 > 8 { + // return true // 禁用這一項。威注音回頭會換候選窗格。 + } + return false + }() - ctlCandidateCurrent?.delegate = nil + ctlCandidateCurrent?.delegate = nil - if useVerticalMode { - ctlCandidateCurrent = .vertical - } else if mgrPrefs.useHorizontalCandidateList { - ctlCandidateCurrent = .horizontal - } else { - ctlCandidateCurrent = .vertical - } + if useVerticalMode { + ctlCandidateCurrent = .vertical + } else if mgrPrefs.useHorizontalCandidateList { + ctlCandidateCurrent = .horizontal + } else { + ctlCandidateCurrent = .vertical + } - // set the attributes for the candidate panel (which uses NSAttributedString) - let textSize = mgrPrefs.candidateListTextSize - let keyLabelSize = max(textSize / 2, kMinKeyLabelSize) + // set the attributes for the candidate panel (which uses NSAttributedString) + let textSize = mgrPrefs.candidateListTextSize + let keyLabelSize = max(textSize / 2, kMinKeyLabelSize) - func labelFont(name: String?, size: CGFloat) -> NSFont { - if let name = name { - return NSFont(name: name, size: size) ?? NSFont.systemFont(ofSize: size) - } - return NSFont.systemFont(ofSize: size) - } + func labelFont(name: String?, size: CGFloat) -> NSFont { + if let name = name { + return NSFont(name: name, size: size) ?? NSFont.systemFont(ofSize: size) + } + return NSFont.systemFont(ofSize: size) + } - func candidateFont(name: String?, size: CGFloat) -> NSFont { - let currentMUIFont = - (keyHandler.inputMode == InputMode.imeModeCHS) - ? "Sarasa Term Slab SC" : "Sarasa Term Slab TC" - var finalReturnFont = - NSFont(name: currentMUIFont, size: size) ?? NSFont.systemFont(ofSize: size) - // 對更紗黑體的依賴到 macOS 11 Big Sur 為止。macOS 12 Monterey 開始則依賴系統內建的函數使用蘋方來處理。 - if #available(macOS 12.0, *) { finalReturnFont = NSFont.systemFont(ofSize: size) } - if let name = name { - return NSFont(name: name, size: size) ?? finalReturnFont - } - return finalReturnFont - } + func candidateFont(name: String?, size: CGFloat) -> NSFont { + let currentMUIFont = + (keyHandler.inputMode == InputMode.imeModeCHS) + ? "Sarasa Term Slab SC" : "Sarasa Term Slab TC" + var finalReturnFont = + NSFont(name: currentMUIFont, size: size) ?? NSFont.systemFont(ofSize: size) + // 對更紗黑體的依賴到 macOS 11 Big Sur 為止。macOS 12 Monterey 開始則依賴系統內建的函數使用蘋方來處理。 + if #available(macOS 12.0, *) { finalReturnFont = NSFont.systemFont(ofSize: size) } + if let name = name { + return NSFont(name: name, size: size) ?? finalReturnFont + } + return finalReturnFont + } - ctlCandidateCurrent?.keyLabelFont = labelFont( - name: mgrPrefs.candidateKeyLabelFontName, size: keyLabelSize - ) - ctlCandidateCurrent?.candidateFont = candidateFont( - name: mgrPrefs.candidateTextFontName, size: textSize - ) + ctlCandidateCurrent?.keyLabelFont = labelFont( + name: mgrPrefs.candidateKeyLabelFontName, size: keyLabelSize + ) + ctlCandidateCurrent?.candidateFont = candidateFont( + name: mgrPrefs.candidateTextFontName, size: textSize + ) - let candidateKeys = mgrPrefs.candidateKeys - let keyLabels = - candidateKeys.count > 4 ? Array(candidateKeys) : Array(mgrPrefs.defaultCandidateKeys) - let keyLabelSuffix = state is InputState.AssociatedPhrases ? "^" : "" - ctlCandidateCurrent?.keyLabels = keyLabels.map { - CandidateKeyLabel(key: String($0), displayedText: String($0) + keyLabelSuffix) - } + let candidateKeys = mgrPrefs.candidateKeys + let keyLabels = + candidateKeys.count > 4 ? Array(candidateKeys) : Array(mgrPrefs.defaultCandidateKeys) + let keyLabelSuffix = state is InputState.AssociatedPhrases ? "^" : "" + ctlCandidateCurrent?.keyLabels = keyLabels.map { + CandidateKeyLabel(key: String($0), displayedText: String($0) + keyLabelSuffix) + } - ctlCandidateCurrent?.delegate = self - ctlCandidateCurrent?.reloadData() - currentClient = client + ctlCandidateCurrent?.delegate = self + ctlCandidateCurrent?.reloadData() + currentClient = client - ctlCandidateCurrent?.visible = true + ctlCandidateCurrent?.visible = true - var lineHeightRect = NSRect(x: 0.0, y: 0.0, width: 16.0, height: 16.0) - var cursor = 0 + var lineHeightRect = NSRect(x: 0.0, y: 0.0, width: 16.0, height: 16.0) + var cursor = 0 - if let state = state as? InputState.ChoosingCandidate { - cursor = Int(state.cursorIndex) - if cursor == state.composingBuffer.count, cursor != 0 { - cursor -= 1 - } - } + if let state = state as? InputState.ChoosingCandidate { + cursor = Int(state.cursorIndex) + if cursor == state.composingBuffer.count, cursor != 0 { + cursor -= 1 + } + } - while lineHeightRect.origin.x == 0, lineHeightRect.origin.y == 0, cursor >= 0 { - (client as? IMKTextInput)?.attributes( - forCharacterIndex: cursor, lineHeightRectangle: &lineHeightRect - ) - cursor -= 1 - } + while lineHeightRect.origin.x == 0, lineHeightRect.origin.y == 0, cursor >= 0 { + (client as? IMKTextInput)?.attributes( + forCharacterIndex: cursor, lineHeightRectangle: &lineHeightRect + ) + cursor -= 1 + } - if useVerticalMode { - ctlCandidateCurrent?.set( - windowTopLeftPoint: NSPoint( - x: lineHeightRect.origin.x + lineHeightRect.size.width + 4.0, y: lineHeightRect.origin.y - 4.0 - ), - bottomOutOfScreenAdjustmentHeight: lineHeightRect.size.height + 4.0 - ) - } else { - ctlCandidateCurrent?.set( - windowTopLeftPoint: NSPoint(x: lineHeightRect.origin.x, y: lineHeightRect.origin.y - 4.0), - bottomOutOfScreenAdjustmentHeight: lineHeightRect.size.height + 4.0 - ) - } - } + if useVerticalMode { + ctlCandidateCurrent?.set( + windowTopLeftPoint: NSPoint( + x: lineHeightRect.origin.x + lineHeightRect.size.width + 4.0, y: lineHeightRect.origin.y - 4.0 + ), + bottomOutOfScreenAdjustmentHeight: lineHeightRect.size.height + 4.0 + ) + } else { + ctlCandidateCurrent?.set( + windowTopLeftPoint: NSPoint(x: lineHeightRect.origin.x, y: lineHeightRect.origin.y - 4.0), + bottomOutOfScreenAdjustmentHeight: lineHeightRect.size.height + 4.0 + ) + } + } - private func show(tooltip: String, composingBuffer: String, cursorIndex: UInt, client: Any!) { - var lineHeightRect = NSRect(x: 0.0, y: 0.0, width: 16.0, height: 16.0) - var cursor = Int(cursorIndex) - if cursor == composingBuffer.count, cursor != 0 { - cursor -= 1 - } - while lineHeightRect.origin.x == 0, lineHeightRect.origin.y == 0, cursor >= 0 { - (client as? IMKTextInput)?.attributes( - forCharacterIndex: cursor, lineHeightRectangle: &lineHeightRect - ) - cursor -= 1 - } - ctlInputMethod.tooltipController.show(tooltip: tooltip, at: lineHeightRect.origin) - } + private func show(tooltip: String, composingBuffer: String, cursorIndex: UInt, client: Any!) { + var lineHeightRect = NSRect(x: 0.0, y: 0.0, width: 16.0, height: 16.0) + var cursor = Int(cursorIndex) + if cursor == composingBuffer.count, cursor != 0 { + cursor -= 1 + } + while lineHeightRect.origin.x == 0, lineHeightRect.origin.y == 0, cursor >= 0 { + (client as? IMKTextInput)?.attributes( + forCharacterIndex: cursor, lineHeightRectangle: &lineHeightRect + ) + cursor -= 1 + } + ctlInputMethod.tooltipController.show(tooltip: tooltip, at: lineHeightRect.origin) + } - private func hideTooltip() { - ctlInputMethod.tooltipController.hide() - } + private func hideTooltip() { + ctlInputMethod.tooltipController.hide() + } } // MARK: - extension ctlInputMethod: KeyHandlerDelegate { - func ctlCandidate(for _: KeyHandler) -> Any { - ctlCandidateCurrent ?? .vertical - } + func ctlCandidate(for _: KeyHandler) -> Any { + ctlCandidateCurrent ?? .vertical + } - func keyHandler( - _: KeyHandler, didSelectCandidateAt index: Int, - ctlCandidate controller: Any - ) { - if let controller = controller as? ctlCandidate { - ctlCandidate(controller, didSelectCandidateAtIndex: UInt(index)) - } - } + func keyHandler( + _: KeyHandler, didSelectCandidateAt index: Int, + ctlCandidate controller: Any + ) { + if let controller = controller as? ctlCandidate { + ctlCandidate(controller, didSelectCandidateAtIndex: UInt(index)) + } + } - func keyHandler(_ keyHandler: KeyHandler, didRequestWriteUserPhraseWith state: InputState) - -> Bool - { - guard let state = state as? InputState.Marking else { - return false - } - if !state.validToWrite { - return false - } - let refInputModeReversed: InputMode = - (keyHandler.inputMode == InputMode.imeModeCHT) - ? InputMode.imeModeCHS : InputMode.imeModeCHT - if !mgrLangModel.writeUserPhrase( - state.userPhrase, inputMode: keyHandler.inputMode, - areWeDuplicating: state.chkIfUserPhraseExists, - areWeDeleting: ctlInputMethod.areWeDeleting - ) - || !mgrLangModel.writeUserPhrase( - state.userPhraseConverted, inputMode: refInputModeReversed, - areWeDuplicating: false, - areWeDeleting: ctlInputMethod.areWeDeleting - ) - { - return false - } - return true - } + func keyHandler(_ keyHandler: KeyHandler, didRequestWriteUserPhraseWith state: InputState) + -> Bool + { + guard let state = state as? InputState.Marking else { + return false + } + if !state.validToWrite { + return false + } + let refInputModeReversed: InputMode = + (keyHandler.inputMode == InputMode.imeModeCHT) + ? InputMode.imeModeCHS : InputMode.imeModeCHT + if !mgrLangModel.writeUserPhrase( + state.userPhrase, inputMode: keyHandler.inputMode, + areWeDuplicating: state.chkIfUserPhraseExists, + areWeDeleting: ctlInputMethod.areWeDeleting + ) + || !mgrLangModel.writeUserPhrase( + state.userPhraseConverted, inputMode: refInputModeReversed, + areWeDuplicating: false, + areWeDeleting: ctlInputMethod.areWeDeleting + ) + { + return false + } + return true + } } // MARK: - extension ctlInputMethod: ctlCandidateDelegate { - func candidateCountForController(_: ctlCandidate) -> UInt { - if let state = state as? InputState.ChoosingCandidate { - return UInt(state.candidates.count) - } else if let state = state as? InputState.AssociatedPhrases { - return UInt(state.candidates.count) - } - return 0 - } + func candidateCountForController(_: ctlCandidate) -> UInt { + if let state = state as? InputState.ChoosingCandidate { + return UInt(state.candidates.count) + } else if let state = state as? InputState.AssociatedPhrases { + return UInt(state.candidates.count) + } + return 0 + } - func ctlCandidate(_: ctlCandidate, candidateAtIndex index: UInt) - -> String - { - if let state = state as? InputState.ChoosingCandidate { - return state.candidates[Int(index)] - } else if let state = state as? InputState.AssociatedPhrases { - return state.candidates[Int(index)] - } - return "" - } + func ctlCandidate(_: ctlCandidate, candidateAtIndex index: UInt) + -> String + { + if let state = state as? InputState.ChoosingCandidate { + return state.candidates[Int(index)] + } else if let state = state as? InputState.AssociatedPhrases { + return state.candidates[Int(index)] + } + return "" + } - func ctlCandidate(_: ctlCandidate, didSelectCandidateAtIndex index: UInt) { - let client = currentClient + func ctlCandidate(_: ctlCandidate, didSelectCandidateAtIndex index: UInt) { + let client = currentClient - if let state = state as? InputState.SymbolTable, - let node = state.node.children?[Int(index)] - { - if let children = node.children, !children.isEmpty { - handle( - state: .SymbolTable(node: node, useVerticalMode: state.useVerticalMode), - client: currentClient - ) - } else { - handle(state: .Committing(poppedText: node.title), client: client) - handle(state: .Empty(), client: client) - } - return - } + if let state = state as? InputState.SymbolTable, + let node = state.node.children?[Int(index)] + { + if let children = node.children, !children.isEmpty { + handle( + state: .SymbolTable(node: node, useVerticalMode: state.useVerticalMode), + client: currentClient + ) + } else { + handle(state: .Committing(poppedText: node.title), client: client) + handle(state: .Empty(), client: client) + } + return + } - if let state = state as? InputState.ChoosingCandidate { - let selectedValue = state.candidates[Int(index)] - keyHandler.fixNode(value: selectedValue) + if let state = state as? InputState.ChoosingCandidate { + let selectedValue = state.candidates[Int(index)] + keyHandler.fixNode(value: selectedValue) - let inputting = keyHandler.buildInputtingState() + let inputting = keyHandler.buildInputtingState() - if mgrPrefs.useSCPCTypingMode { - keyHandler.clear() - let composingBuffer = inputting.composingBuffer - handle(state: .Committing(poppedText: composingBuffer), client: client) - if mgrPrefs.associatedPhrasesEnabled, - let associatePhrases = keyHandler.buildAssociatePhraseState( - withKey: composingBuffer, useVerticalMode: state.useVerticalMode - ), !associatePhrases.candidates.isEmpty - { - handle(state: associatePhrases, client: client) - } else { - handle(state: .Empty(), client: client) - } - } else { - handle(state: inputting, client: client) - } - return - } + if mgrPrefs.useSCPCTypingMode { + keyHandler.clear() + let composingBuffer = inputting.composingBuffer + handle(state: .Committing(poppedText: composingBuffer), client: client) + if mgrPrefs.associatedPhrasesEnabled, + let associatePhrases = keyHandler.buildAssociatePhraseState( + withKey: composingBuffer, useVerticalMode: state.useVerticalMode + ), !associatePhrases.candidates.isEmpty + { + handle(state: associatePhrases, client: client) + } else { + handle(state: .Empty(), client: client) + } + } else { + handle(state: inputting, client: client) + } + return + } - if let state = state as? InputState.AssociatedPhrases { - let selectedValue = state.candidates[Int(index)] - handle(state: .Committing(poppedText: selectedValue), client: currentClient) - if mgrPrefs.associatedPhrasesEnabled, - let associatePhrases = keyHandler.buildAssociatePhraseState( - withKey: selectedValue, useVerticalMode: state.useVerticalMode - ), !associatePhrases.candidates.isEmpty - { - handle(state: associatePhrases, client: client) - } else { - handle(state: .Empty(), client: client) - } - } - } + if let state = state as? InputState.AssociatedPhrases { + let selectedValue = state.candidates[Int(index)] + handle(state: .Committing(poppedText: selectedValue), client: currentClient) + if mgrPrefs.associatedPhrasesEnabled, + let associatePhrases = keyHandler.buildAssociatePhraseState( + withKey: selectedValue, useVerticalMode: state.useVerticalMode + ), !associatePhrases.candidates.isEmpty + { + handle(state: associatePhrases, client: client) + } else { + handle(state: .Empty(), client: client) + } + } + } } diff --git a/Source/Modules/IMEModules/ctlInputMethod_Menu.swift b/Source/Modules/IMEModules/ctlInputMethod_Menu.swift index ab53cdf8..eb08908b 100644 --- a/Source/Modules/IMEModules/ctlInputMethod_Menu.swift +++ b/Source/Modules/IMEModules/ctlInputMethod_Menu.swift @@ -27,9 +27,9 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import Cocoa extension Bool { - fileprivate var state: NSControl.StateValue { - self ? .on : .off - } + fileprivate var state: NSControl.StateValue { + self ? .on : .off + } } // MARK: - IME Menu Manager @@ -37,320 +37,321 @@ extension Bool { // 因為選單部分的內容又臭又長,所以就單獨拉到一個檔案內管理了。 extension ctlInputMethod { - override func menu() -> NSMenu! { - let optionKeyPressed = NSEvent.modifierFlags.contains(.option) + override func menu() -> NSMenu! { + let optionKeyPressed = NSEvent.modifierFlags.contains(.option) - let menu = NSMenu(title: "Input Method Menu") + let menu = NSMenu(title: "Input Method Menu") - let useSCPCTypingModeItem = menu.addItem( - withTitle: NSLocalizedString("Per-Char Select Mode", comment: ""), - action: #selector(toggleSCPCTypingMode(_:)), keyEquivalent: "P" - ) - useSCPCTypingModeItem.keyEquivalentModifierMask = [.command, .control] - useSCPCTypingModeItem.state = mgrPrefs.useSCPCTypingMode.state + let useSCPCTypingModeItem = menu.addItem( + withTitle: NSLocalizedString("Per-Char Select Mode", comment: ""), + action: #selector(toggleSCPCTypingMode(_:)), keyEquivalent: "P" + ) + useSCPCTypingModeItem.keyEquivalentModifierMask = [.command, .control] + useSCPCTypingModeItem.state = mgrPrefs.useSCPCTypingMode.state - let userAssociatedPhrasesItem = menu.addItem( - withTitle: NSLocalizedString("Per-Char Associated Phrases", comment: ""), - action: #selector(toggleAssociatedPhrasesEnabled(_:)), keyEquivalent: "O" - ) - userAssociatedPhrasesItem.keyEquivalentModifierMask = [.command, .control] - userAssociatedPhrasesItem.state = mgrPrefs.associatedPhrasesEnabled.state + let userAssociatedPhrasesItem = menu.addItem( + withTitle: NSLocalizedString("Per-Char Associated Phrases", comment: ""), + action: #selector(toggleAssociatedPhrasesEnabled(_:)), keyEquivalent: "O" + ) + userAssociatedPhrasesItem.keyEquivalentModifierMask = [.command, .control] + userAssociatedPhrasesItem.state = mgrPrefs.associatedPhrasesEnabled.state - let useCNS11643SupportItem = menu.addItem( - withTitle: NSLocalizedString("CNS11643 Mode", comment: ""), - action: #selector(toggleCNS11643Enabled(_:)), keyEquivalent: "L" - ) - useCNS11643SupportItem.keyEquivalentModifierMask = [.command, .control] - useCNS11643SupportItem.state = mgrPrefs.cns11643Enabled.state + let useCNS11643SupportItem = menu.addItem( + withTitle: NSLocalizedString("CNS11643 Mode", comment: ""), + action: #selector(toggleCNS11643Enabled(_:)), keyEquivalent: "L" + ) + useCNS11643SupportItem.keyEquivalentModifierMask = [.command, .control] + useCNS11643SupportItem.state = mgrPrefs.cns11643Enabled.state - if IME.getInputMode() == InputMode.imeModeCHT { - let chineseConversionItem = menu.addItem( - withTitle: NSLocalizedString("Force KangXi Writing", comment: ""), - action: #selector(toggleChineseConverter(_:)), keyEquivalent: "K" - ) - chineseConversionItem.keyEquivalentModifierMask = [.command, .control] - chineseConversionItem.state = mgrPrefs.chineseConversionEnabled.state + if IME.getInputMode() == InputMode.imeModeCHT { + let chineseConversionItem = menu.addItem( + withTitle: NSLocalizedString("Force KangXi Writing", comment: ""), + action: #selector(toggleChineseConverter(_:)), keyEquivalent: "K" + ) + chineseConversionItem.keyEquivalentModifierMask = [.command, .control] + chineseConversionItem.state = mgrPrefs.chineseConversionEnabled.state - let shiftJISConversionItem = menu.addItem( - withTitle: NSLocalizedString("JIS Shinjitai Output", comment: ""), - action: #selector(toggleShiftJISShinjitaiOutput(_:)), keyEquivalent: "J" - ) - shiftJISConversionItem.keyEquivalentModifierMask = [.command, .control] - shiftJISConversionItem.state = mgrPrefs.shiftJISShinjitaiOutputEnabled.state - } + let shiftJISConversionItem = menu.addItem( + withTitle: NSLocalizedString("JIS Shinjitai Output", comment: ""), + action: #selector(toggleShiftJISShinjitaiOutput(_:)), keyEquivalent: "J" + ) + shiftJISConversionItem.keyEquivalentModifierMask = [.command, .control] + shiftJISConversionItem.state = mgrPrefs.shiftJISShinjitaiOutputEnabled.state + } - let halfWidthPunctuationItem = menu.addItem( - withTitle: NSLocalizedString("Half-Width Punctuation Mode", comment: ""), - action: #selector(toggleHalfWidthPunctuation(_:)), keyEquivalent: "H" - ) - halfWidthPunctuationItem.keyEquivalentModifierMask = [.command, .control] - halfWidthPunctuationItem.state = mgrPrefs.halfWidthPunctuationEnabled.state + let halfWidthPunctuationItem = menu.addItem( + withTitle: NSLocalizedString("Half-Width Punctuation Mode", comment: ""), + action: #selector(toggleHalfWidthPunctuation(_:)), keyEquivalent: "H" + ) + halfWidthPunctuationItem.keyEquivalentModifierMask = [.command, .control] + halfWidthPunctuationItem.state = mgrPrefs.halfWidthPunctuationEnabled.state - if optionKeyPressed { - let phaseReplacementItem = menu.addItem( - withTitle: NSLocalizedString("Use Phrase Replacement", comment: ""), - action: #selector(togglePhraseReplacement(_:)), keyEquivalent: "" - ) - phaseReplacementItem.state = mgrPrefs.phraseReplacementEnabled.state + if optionKeyPressed || mgrPrefs.phraseReplacementEnabled { + let phaseReplacementItem = menu.addItem( + withTitle: NSLocalizedString("Use Phrase Replacement", comment: ""), + action: #selector(togglePhraseReplacement(_:)), keyEquivalent: "" + ) + phaseReplacementItem.state = mgrPrefs.phraseReplacementEnabled.state + } - let toggleSymbolInputItem = menu.addItem( - withTitle: NSLocalizedString("Symbol & Emoji Input", comment: ""), - action: #selector(toggleSymbolEnabled(_:)), keyEquivalent: "" - ) - toggleSymbolInputItem.state = mgrPrefs.symbolInputEnabled.state - } + if optionKeyPressed { + let toggleSymbolInputItem = menu.addItem( + withTitle: NSLocalizedString("Symbol & Emoji Input", comment: ""), + action: #selector(toggleSymbolEnabled(_:)), keyEquivalent: "" + ) + toggleSymbolInputItem.state = mgrPrefs.symbolInputEnabled.state + } - menu.addItem(NSMenuItem.separator()) // --------------------- + menu.addItem(NSMenuItem.separator()) // --------------------- - menu.addItem( - withTitle: NSLocalizedString("Open User Data Folder", comment: ""), - action: #selector(openUserDataFolder(_:)), keyEquivalent: "" - ) - menu.addItem( - withTitle: NSLocalizedString("Edit User Phrases…", comment: ""), - action: #selector(openUserPhrases(_:)), keyEquivalent: "" - ) - menu.addItem( - withTitle: NSLocalizedString("Edit Excluded Phrases…", comment: ""), - action: #selector(openExcludedPhrases(_:)), keyEquivalent: "" - ) + menu.addItem( + withTitle: NSLocalizedString("Open User Data Folder", comment: ""), + action: #selector(openUserDataFolder(_:)), keyEquivalent: "" + ) + menu.addItem( + withTitle: NSLocalizedString("Edit User Phrases…", comment: ""), + action: #selector(openUserPhrases(_:)), keyEquivalent: "" + ) + menu.addItem( + withTitle: NSLocalizedString("Edit Excluded Phrases…", comment: ""), + action: #selector(openExcludedPhrases(_:)), keyEquivalent: "" + ) - if optionKeyPressed { - menu.addItem( - withTitle: NSLocalizedString("Edit Phrase Replacement Table…", comment: ""), - action: #selector(openPhraseReplacement(_:)), keyEquivalent: "" - ) - menu.addItem( - withTitle: NSLocalizedString("Edit Associated Phrases…", comment: ""), - action: #selector(openAssociatedPhrases(_:)), keyEquivalent: "" - ) - menu.addItem( - withTitle: NSLocalizedString("Edit User Symbol & Emoji Data…", comment: ""), - action: #selector(openUserSymbols(_:)), keyEquivalent: "" - ) - } + if optionKeyPressed { + menu.addItem( + withTitle: NSLocalizedString("Edit Phrase Replacement Table…", comment: ""), + action: #selector(openPhraseReplacement(_:)), keyEquivalent: "" + ) + menu.addItem( + withTitle: NSLocalizedString("Edit Associated Phrases…", comment: ""), + action: #selector(openAssociatedPhrases(_:)), keyEquivalent: "" + ) + menu.addItem( + withTitle: NSLocalizedString("Edit User Symbol & Emoji Data…", comment: ""), + action: #selector(openUserSymbols(_:)), keyEquivalent: "" + ) + } - if optionKeyPressed || !mgrPrefs.shouldAutoReloadUserDataFiles { - menu.addItem( - withTitle: NSLocalizedString("Reload User Phrases", comment: ""), - action: #selector(reloadUserPhrases(_:)), keyEquivalent: "" - ) - } + if optionKeyPressed || !mgrPrefs.shouldAutoReloadUserDataFiles { + menu.addItem( + withTitle: NSLocalizedString("Reload User Phrases", comment: ""), + action: #selector(reloadUserPhrases(_:)), keyEquivalent: "" + ) + } - menu.addItem(NSMenuItem.separator()) // --------------------- + menu.addItem(NSMenuItem.separator()) // --------------------- - if optionKeyPressed { - menu.addItem( - withTitle: NSLocalizedString("vChewing Preferences…", comment: ""), - action: #selector(showLegacyPreferences(_:)), keyEquivalent: "" - ) - } else { - menu.addItem( - withTitle: NSLocalizedString("vChewing Preferences…", comment: ""), - action: #selector(showPreferences(_:)), keyEquivalent: "" - ) - menu.addItem( - withTitle: NSLocalizedString("Check for Updates…", comment: ""), - action: #selector(checkForUpdate(_:)), keyEquivalent: "" - ) - } - menu.addItem( - withTitle: NSLocalizedString("Reboot vChewing…", comment: ""), - action: #selector(selfTerminate(_:)), keyEquivalent: "" - ) - menu.addItem( - withTitle: NSLocalizedString("About vChewing…", comment: ""), - action: #selector(showAbout(_:)), keyEquivalent: "" - ) - if optionKeyPressed { - menu.addItem( - withTitle: NSLocalizedString("Uninstall vChewing…", comment: ""), - action: #selector(selfUninstall(_:)), keyEquivalent: "" - ) - } + if optionKeyPressed { + menu.addItem( + withTitle: NSLocalizedString("vChewing Preferences…", comment: ""), + action: #selector(showLegacyPreferences(_:)), keyEquivalent: "" + ) + } else { + menu.addItem( + withTitle: NSLocalizedString("vChewing Preferences…", comment: ""), + action: #selector(showPreferences(_:)), keyEquivalent: "" + ) + menu.addItem( + withTitle: NSLocalizedString("Check for Updates…", comment: ""), + action: #selector(checkForUpdate(_:)), keyEquivalent: "" + ) + } + menu.addItem( + withTitle: NSLocalizedString("Reboot vChewing…", comment: ""), + action: #selector(selfTerminate(_:)), keyEquivalent: "" + ) + menu.addItem( + withTitle: NSLocalizedString("About vChewing…", comment: ""), + action: #selector(showAbout(_:)), keyEquivalent: "" + ) + if optionKeyPressed { + menu.addItem( + withTitle: NSLocalizedString("Uninstall vChewing…", comment: ""), + action: #selector(selfUninstall(_:)), keyEquivalent: "" + ) + } - // NSMenu 會阻止任何 modified key 相關的訊號傳回輸入法,所以咱們在此重設鍵盤佈局 - setKeyLayout() + // NSMenu 會阻止任何 modified key 相關的訊號傳回輸入法,所以咱們在此重設鍵盤佈局 + setKeyLayout() - return menu - } + return menu + } - // MARK: - IME Menu Items + // MARK: - IME Menu Items - @objc override func showPreferences(_: Any?) { - if #available(macOS 11.0, *) { - NSApp.setActivationPolicy(.accessory) - ctlPrefUI.shared.controller.show(preferencePane: Preferences.PaneIdentifier(rawValue: "General")) - ctlPrefUI.shared.controller.window?.level = .floating - } else { - showPrefWindowTraditional() - } - } + @objc override func showPreferences(_: Any?) { + if #available(macOS 11.0, *) { + NSApp.setActivationPolicy(.accessory) + ctlPrefUI.shared.controller.show(preferencePane: Preferences.PaneIdentifier(rawValue: "General")) + ctlPrefUI.shared.controller.window?.level = .floating + } else { + showPrefWindowTraditional() + } + } - @objc func showLegacyPreferences(_: Any?) { - showPrefWindowTraditional() - } + @objc func showLegacyPreferences(_: Any?) { + showPrefWindowTraditional() + } - private func showPrefWindowTraditional() { - (NSApp.delegate as? AppDelegate)?.showPreferences() - NSApp.activate(ignoringOtherApps: true) - } + private func showPrefWindowTraditional() { + (NSApp.delegate as? AppDelegate)?.showPreferences() + NSApp.activate(ignoringOtherApps: true) + } - @objc func toggleSCPCTypingMode(_: Any?) { - NotifierController.notify( - message: String( - format: "%@%@%@", NSLocalizedString("Per-Char Select Mode", comment: ""), "\n", - mgrPrefs.toggleSCPCTypingModeEnabled() - ? NSLocalizedString("NotificationSwitchON", comment: "") - : NSLocalizedString("NotificationSwitchOFF", comment: "") - )) - resetKeyHandler() - } + @objc func toggleSCPCTypingMode(_: Any?) { + NotifierController.notify( + message: String( + format: "%@%@%@", NSLocalizedString("Per-Char Select Mode", comment: ""), "\n", + mgrPrefs.toggleSCPCTypingModeEnabled() + ? NSLocalizedString("NotificationSwitchON", comment: "") + : NSLocalizedString("NotificationSwitchOFF", comment: "") + )) + resetKeyHandler() + } - @objc func toggleChineseConverter(_: Any?) { - NotifierController.notify( - message: String( - format: "%@%@%@", NSLocalizedString("Force KangXi Writing", comment: ""), "\n", - mgrPrefs.toggleChineseConversionEnabled() - ? NSLocalizedString("NotificationSwitchON", comment: "") - : NSLocalizedString("NotificationSwitchOFF", comment: "") - )) - resetKeyHandler() - } + @objc func toggleChineseConverter(_: Any?) { + NotifierController.notify( + message: String( + format: "%@%@%@", NSLocalizedString("Force KangXi Writing", comment: ""), "\n", + mgrPrefs.toggleChineseConversionEnabled() + ? NSLocalizedString("NotificationSwitchON", comment: "") + : NSLocalizedString("NotificationSwitchOFF", comment: "") + )) + resetKeyHandler() + } - @objc func toggleShiftJISShinjitaiOutput(_: Any?) { - NotifierController.notify( - message: String( - format: "%@%@%@", NSLocalizedString("JIS Shinjitai Output", comment: ""), "\n", - mgrPrefs.toggleShiftJISShinjitaiOutputEnabled() - ? NSLocalizedString("NotificationSwitchON", comment: "") - : NSLocalizedString("NotificationSwitchOFF", comment: "") - )) - resetKeyHandler() - } + @objc func toggleShiftJISShinjitaiOutput(_: Any?) { + NotifierController.notify( + message: String( + format: "%@%@%@", NSLocalizedString("JIS Shinjitai Output", comment: ""), "\n", + mgrPrefs.toggleShiftJISShinjitaiOutputEnabled() + ? NSLocalizedString("NotificationSwitchON", comment: "") + : NSLocalizedString("NotificationSwitchOFF", comment: "") + )) + resetKeyHandler() + } - @objc func toggleHalfWidthPunctuation(_: Any?) { - NotifierController.notify( - message: String( - format: "%@%@%@", NSLocalizedString("Half-Width Punctuation Mode", comment: ""), - "\n", - mgrPrefs.toggleHalfWidthPunctuationEnabled() - ? NSLocalizedString("NotificationSwitchON", comment: "") - : NSLocalizedString("NotificationSwitchOFF", comment: "") - )) - resetKeyHandler() - } + @objc func toggleHalfWidthPunctuation(_: Any?) { + NotifierController.notify( + message: String( + format: "%@%@%@", NSLocalizedString("Half-Width Punctuation Mode", comment: ""), + "\n", + mgrPrefs.toggleHalfWidthPunctuationEnabled() + ? NSLocalizedString("NotificationSwitchON", comment: "") + : NSLocalizedString("NotificationSwitchOFF", comment: "") + )) + resetKeyHandler() + } - @objc func toggleCNS11643Enabled(_: Any?) { - NotifierController.notify( - message: String( - format: "%@%@%@", NSLocalizedString("CNS11643 Mode", comment: ""), "\n", - mgrPrefs.toggleCNS11643Enabled() - ? NSLocalizedString("NotificationSwitchON", comment: "") - : NSLocalizedString("NotificationSwitchOFF", comment: "") - )) - resetKeyHandler() - } + @objc func toggleCNS11643Enabled(_: Any?) { + NotifierController.notify( + message: String( + format: "%@%@%@", NSLocalizedString("CNS11643 Mode", comment: ""), "\n", + mgrPrefs.toggleCNS11643Enabled() + ? NSLocalizedString("NotificationSwitchON", comment: "") + : NSLocalizedString("NotificationSwitchOFF", comment: "") + )) + resetKeyHandler() + } - @objc func toggleSymbolEnabled(_: Any?) { - NotifierController.notify( - message: String( - format: "%@%@%@", NSLocalizedString("Symbol & Emoji Input", comment: ""), "\n", - mgrPrefs.toggleSymbolInputEnabled() - ? NSLocalizedString("NotificationSwitchON", comment: "") - : NSLocalizedString("NotificationSwitchOFF", comment: "") - )) - resetKeyHandler() - } + @objc func toggleSymbolEnabled(_: Any?) { + NotifierController.notify( + message: String( + format: "%@%@%@", NSLocalizedString("Symbol & Emoji Input", comment: ""), "\n", + mgrPrefs.toggleSymbolInputEnabled() + ? NSLocalizedString("NotificationSwitchON", comment: "") + : NSLocalizedString("NotificationSwitchOFF", comment: "") + )) + resetKeyHandler() + } - @objc func toggleAssociatedPhrasesEnabled(_: Any?) { - NotifierController.notify( - message: String( - format: "%@%@%@", NSLocalizedString("Per-Char Associated Phrases", comment: ""), - "\n", - mgrPrefs.toggleAssociatedPhrasesEnabled() - ? NSLocalizedString("NotificationSwitchON", comment: "") - : NSLocalizedString("NotificationSwitchOFF", comment: "") - )) - resetKeyHandler() - } + @objc func toggleAssociatedPhrasesEnabled(_: Any?) { + NotifierController.notify( + message: String( + format: "%@%@%@", NSLocalizedString("Per-Char Associated Phrases", comment: ""), + "\n", + mgrPrefs.toggleAssociatedPhrasesEnabled() + ? NSLocalizedString("NotificationSwitchON", comment: "") + : NSLocalizedString("NotificationSwitchOFF", comment: "") + )) + resetKeyHandler() + } - @objc func togglePhraseReplacement(_: Any?) { - NotifierController.notify( - message: String( - format: "%@%@%@", NSLocalizedString("Use Phrase Replacement", comment: ""), "\n", - mgrPrefs.togglePhraseReplacementEnabled() - ? NSLocalizedString("NotificationSwitchON", comment: "") - : NSLocalizedString("NotificationSwitchOFF", comment: "") - )) - resetKeyHandler() - } + @objc func togglePhraseReplacement(_: Any?) { + NotifierController.notify( + message: String( + format: "%@%@%@", NSLocalizedString("Use Phrase Replacement", comment: ""), "\n", + mgrPrefs.togglePhraseReplacementEnabled() + ? NSLocalizedString("NotificationSwitchON", comment: "") + : NSLocalizedString("NotificationSwitchOFF", comment: "") + )) + resetKeyHandler() + } - @objc func selfUninstall(_: Any?) { - (NSApp.delegate as? AppDelegate)?.selfUninstall() - } + @objc func selfUninstall(_: Any?) { + (NSApp.delegate as? AppDelegate)?.selfUninstall() + } - @objc func selfTerminate(_: Any?) { - NSApp.activate(ignoringOtherApps: true) - NSApp.terminate(nil) - } + @objc func selfTerminate(_: Any?) { + NSApp.activate(ignoringOtherApps: true) + NSApp.terminate(nil) + } - @objc func checkForUpdate(_: Any?) { - (NSApp.delegate as? AppDelegate)?.checkForUpdate(forced: true) - } + @objc func checkForUpdate(_: Any?) { + (NSApp.delegate as? AppDelegate)?.checkForUpdate(forced: true) + } - @objc func openUserDataFolder(_: Any?) { - if !mgrLangModel.checkIfUserDataFolderExists() { - return - } - NSWorkspace.shared.openFile( - mgrLangModel.dataFolderPath(isDefaultFolder: false), withApplication: "Finder" - ) - } + @objc func openUserDataFolder(_: Any?) { + if !mgrLangModel.checkIfUserDataFolderExists() { + return + } + NSWorkspace.shared.openFile( + mgrLangModel.dataFolderPath(isDefaultFolder: false), withApplication: "Finder" + ) + } - @objc func openUserPhrases(_: Any?) { - IME.openPhraseFile(userFileAt: mgrLangModel.userPhrasesDataPath(IME.getInputMode())) - if NSEvent.modifierFlags.contains(.option), mgrPrefs.isDebugModeEnabled { - IME.openPhraseFile(userFileAt: mgrLangModel.userPhrasesDataPath(IME.getInputMode(isReversed: true))) - } - } + @objc func openUserPhrases(_: Any?) { + IME.openPhraseFile(userFileAt: mgrLangModel.userPhrasesDataPath(IME.getInputMode())) + if NSEvent.modifierFlags.contains(.option), mgrPrefs.isDebugModeEnabled { + IME.openPhraseFile(userFileAt: mgrLangModel.userPhrasesDataPath(IME.getInputMode(isReversed: true))) + } + } - @objc func openExcludedPhrases(_: Any?) { - IME.openPhraseFile(userFileAt: mgrLangModel.excludedPhrasesDataPath(IME.getInputMode())) - if NSEvent.modifierFlags.contains(.option), mgrPrefs.isDebugModeEnabled { - IME.openPhraseFile(userFileAt: mgrLangModel.excludedPhrasesDataPath(IME.getInputMode(isReversed: true))) - } - } + @objc func openExcludedPhrases(_: Any?) { + IME.openPhraseFile(userFileAt: mgrLangModel.excludedPhrasesDataPath(IME.getInputMode())) + if NSEvent.modifierFlags.contains(.option), mgrPrefs.isDebugModeEnabled { + IME.openPhraseFile(userFileAt: mgrLangModel.excludedPhrasesDataPath(IME.getInputMode(isReversed: true))) + } + } - @objc func openUserSymbols(_: Any?) { - IME.openPhraseFile(userFileAt: mgrLangModel.userSymbolDataPath(IME.getInputMode())) - if NSEvent.modifierFlags.contains(.option), mgrPrefs.isDebugModeEnabled { - IME.openPhraseFile(userFileAt: mgrLangModel.userSymbolDataPath(IME.getInputMode(isReversed: true))) - } - } + @objc func openUserSymbols(_: Any?) { + IME.openPhraseFile(userFileAt: mgrLangModel.userSymbolDataPath(IME.getInputMode())) + if NSEvent.modifierFlags.contains(.option), mgrPrefs.isDebugModeEnabled { + IME.openPhraseFile(userFileAt: mgrLangModel.userSymbolDataPath(IME.getInputMode(isReversed: true))) + } + } - @objc func openPhraseReplacement(_: Any?) { - IME.openPhraseFile(userFileAt: mgrLangModel.phraseReplacementDataPath(IME.getInputMode())) - if NSEvent.modifierFlags.contains(.option), mgrPrefs.isDebugModeEnabled { - IME.openPhraseFile(userFileAt: mgrLangModel.phraseReplacementDataPath(IME.getInputMode(isReversed: true))) - } - } + @objc func openPhraseReplacement(_: Any?) { + IME.openPhraseFile(userFileAt: mgrLangModel.phraseReplacementDataPath(IME.getInputMode())) + if NSEvent.modifierFlags.contains(.option), mgrPrefs.isDebugModeEnabled { + IME.openPhraseFile(userFileAt: mgrLangModel.phraseReplacementDataPath(IME.getInputMode(isReversed: true))) + } + } - @objc func openAssociatedPhrases(_: Any?) { - IME.openPhraseFile(userFileAt: mgrLangModel.userAssociatedPhrasesDataPath(IME.getInputMode())) - if NSEvent.modifierFlags.contains(.option), mgrPrefs.isDebugModeEnabled { - IME.openPhraseFile( - userFileAt: mgrLangModel.userAssociatedPhrasesDataPath(IME.getInputMode(isReversed: true))) - } - } + @objc func openAssociatedPhrases(_: Any?) { + IME.openPhraseFile(userFileAt: mgrLangModel.userAssociatedPhrasesDataPath(IME.getInputMode())) + if NSEvent.modifierFlags.contains(.option), mgrPrefs.isDebugModeEnabled { + IME.openPhraseFile( + userFileAt: mgrLangModel.userAssociatedPhrasesDataPath(IME.getInputMode(isReversed: true))) + } + } - @objc func reloadUserPhrases(_: Any?) { - mgrLangModel.loadUserPhrases() - mgrLangModel.loadUserPhraseReplacement() - } + @objc func reloadUserPhrases(_: Any?) { + IME.initLangModels(userOnly: true) + } - @objc func showAbout(_: Any?) { - (NSApp.delegate as? AppDelegate)?.showAbout() - NSApp.activate(ignoringOtherApps: true) - } + @objc func showAbout(_: Any?) { + (NSApp.delegate as? AppDelegate)?.showAbout() + NSApp.activate(ignoringOtherApps: true) + } } diff --git a/Source/Modules/IMEModules/mgrPrefs.swift b/Source/Modules/IMEModules/mgrPrefs.swift index 6c75b4b6..6f5dee5c 100644 --- a/Source/Modules/IMEModules/mgrPrefs.swift +++ b/Source/Modules/IMEModules/mgrPrefs.swift @@ -27,39 +27,39 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import Cocoa struct UserDef { - static let kIsDebugModeEnabled = "_DebugMode" - static let kMostRecentInputMode = "MostRecentInputMode" - static let kUserDataFolderSpecified = "UserDataFolderSpecified" - static let kCheckUpdateAutomatically = "CheckUpdateAutomatically" - static let kMandarinParser = "MandarinParser" - static let kBasicKeyboardLayout = "BasicKeyboardLayout" - static let kShowPageButtonsInCandidateWindow = "ShowPageButtonsInCandidateWindow" - static let kCandidateListTextSize = "CandidateListTextSize" - static let kAppleLanguages = "AppleLanguages" - static let kShouldAutoReloadUserDataFiles = "ShouldAutoReloadUserDataFiles" - static let kSetRearCursorMode = "SetRearCursorMode" - static let kUseHorizontalCandidateList = "UseHorizontalCandidateList" - static let kComposingBufferSize = "ComposingBufferSize" - static let kChooseCandidateUsingSpace = "ChooseCandidateUsingSpace" - static let kCNS11643Enabled = "CNS11643Enabled" - static let kSymbolInputEnabled = "SymbolInputEnabled" - static let kChineseConversionEnabled = "ChineseConversionEnabled" - static let kShiftJISShinjitaiOutputEnabled = "ShiftJISShinjitaiOutputEnabled" - static let kHalfWidthPunctuationEnabled = "HalfWidthPunctuationEnable" - static let kMoveCursorAfterSelectingCandidate = "MoveCursorAfterSelectingCandidate" - static let kEscToCleanInputBuffer = "EscToCleanInputBuffer" - static let kSpecifyShiftTabKeyBehavior = "SpecifyShiftTabKeyBehavior" - static let kSpecifyShiftSpaceKeyBehavior = "SpecifyShiftSpaceKeyBehavior" - static let kUseSCPCTypingMode = "UseSCPCTypingMode" - static let kMaxCandidateLength = "MaxCandidateLength" - static let kShouldNotFartInLieuOfBeep = "ShouldNotFartInLieuOfBeep" + static let kIsDebugModeEnabled = "_DebugMode" + static let kMostRecentInputMode = "MostRecentInputMode" + static let kUserDataFolderSpecified = "UserDataFolderSpecified" + static let kCheckUpdateAutomatically = "CheckUpdateAutomatically" + static let kMandarinParser = "MandarinParser" + static let kBasicKeyboardLayout = "BasicKeyboardLayout" + static let kShowPageButtonsInCandidateWindow = "ShowPageButtonsInCandidateWindow" + static let kCandidateListTextSize = "CandidateListTextSize" + static let kAppleLanguages = "AppleLanguages" + static let kShouldAutoReloadUserDataFiles = "ShouldAutoReloadUserDataFiles" + static let kSetRearCursorMode = "SetRearCursorMode" + static let kUseHorizontalCandidateList = "UseHorizontalCandidateList" + static let kComposingBufferSize = "ComposingBufferSize" + static let kChooseCandidateUsingSpace = "ChooseCandidateUsingSpace" + static let kCNS11643Enabled = "CNS11643Enabled" + static let kSymbolInputEnabled = "SymbolInputEnabled" + static let kChineseConversionEnabled = "ChineseConversionEnabled" + static let kShiftJISShinjitaiOutputEnabled = "ShiftJISShinjitaiOutputEnabled" + static let kHalfWidthPunctuationEnabled = "HalfWidthPunctuationEnable" + static let kMoveCursorAfterSelectingCandidate = "MoveCursorAfterSelectingCandidate" + static let kEscToCleanInputBuffer = "EscToCleanInputBuffer" + static let kSpecifyShiftTabKeyBehavior = "SpecifyShiftTabKeyBehavior" + static let kSpecifyShiftSpaceKeyBehavior = "SpecifyShiftSpaceKeyBehavior" + static let kUseSCPCTypingMode = "UseSCPCTypingMode" + static let kMaxCandidateLength = "MaxCandidateLength" + static let kShouldNotFartInLieuOfBeep = "ShouldNotFartInLieuOfBeep" - static let kCandidateTextFontName = "CandidateTextFontName" - static let kCandidateKeyLabelFontName = "CandidateKeyLabelFontName" - static let kCandidateKeys = "CandidateKeys" + static let kCandidateTextFontName = "CandidateTextFontName" + static let kCandidateKeyLabelFontName = "CandidateKeyLabelFontName" + static let kCandidateKeys = "CandidateKeys" - static let kAssociatedPhrasesEnabled = "AssociatedPhrasesEnabled" - static let kPhraseReplacementEnabled = "PhraseReplacementEnabled" + static let kAssociatedPhrasesEnabled = "AssociatedPhrasesEnabled" + static let kPhraseReplacementEnabled = "PhraseReplacementEnabled" } private let kDefaultCandidateListTextSize: CGFloat = 18 @@ -80,441 +80,441 @@ private let kDefaultKeys = "123456789" // MARK: - UserDefaults extension. -@objc extension UserDefaults { - func setDefault(_ value: Any?, forKey defaultName: String) { - if object(forKey: defaultName) == nil { - set(value, forKey: defaultName) - } - } +extension UserDefaults { + func setDefault(_ value: Any?, forKey defaultName: String) { + if object(forKey: defaultName) == nil { + set(value, forKey: defaultName) + } + } } // MARK: - Property wrappers @propertyWrapper struct UserDefault { - let key: String - let defaultValue: Value - var container: UserDefaults = .standard + let key: String + let defaultValue: Value + var container: UserDefaults = .standard - var wrappedValue: Value { - get { - container.object(forKey: key) as? Value ?? defaultValue - } - set { - container.set(newValue, forKey: key) - } - } + var wrappedValue: Value { + get { + container.object(forKey: key) as? Value ?? defaultValue + } + set { + container.set(newValue, forKey: key) + } + } } @propertyWrapper struct CandidateListTextSize { - let key: String - let defaultValue: CGFloat = kDefaultCandidateListTextSize - lazy var container: UserDefault = .init(key: key, defaultValue: defaultValue) + let key: String + let defaultValue: CGFloat = kDefaultCandidateListTextSize + lazy var container: UserDefault = .init(key: key, defaultValue: defaultValue) - var wrappedValue: CGFloat { - mutating get { - var value = container.wrappedValue - if value < kMinCandidateListTextSize { - value = kMinCandidateListTextSize - } else if value > kMaxCandidateListTextSize { - value = kMaxCandidateListTextSize - } - return value - } - set { - var value = newValue - if value < kMinCandidateListTextSize { - value = kMinCandidateListTextSize - } else if value > kMaxCandidateListTextSize { - value = kMaxCandidateListTextSize - } - container.wrappedValue = value - } - } + var wrappedValue: CGFloat { + mutating get { + var value = container.wrappedValue + if value < kMinCandidateListTextSize { + value = kMinCandidateListTextSize + } else if value > kMaxCandidateListTextSize { + value = kMaxCandidateListTextSize + } + return value + } + set { + var value = newValue + if value < kMinCandidateListTextSize { + value = kMinCandidateListTextSize + } else if value > kMaxCandidateListTextSize { + value = kMaxCandidateListTextSize + } + container.wrappedValue = value + } + } } @propertyWrapper struct ComposingBufferSize { - let key: String - let defaultValue: Int = kDefaultComposingBufferSize - lazy var container: UserDefault = .init(key: key, defaultValue: defaultValue) + let key: String + let defaultValue: Int = kDefaultComposingBufferSize + lazy var container: UserDefault = .init(key: key, defaultValue: defaultValue) - var wrappedValue: Int { - mutating get { - let currentValue = container.wrappedValue - if currentValue < kMinComposingBufferSize { - return kMinComposingBufferSize - } else if currentValue > kMaxComposingBufferSize { - return kMaxComposingBufferSize - } - return currentValue - } - set { - var value = newValue - if value < kMinComposingBufferSize { - value = kMinComposingBufferSize - } else if value > kMaxComposingBufferSize { - value = kMaxComposingBufferSize - } - container.wrappedValue = value - } - } + var wrappedValue: Int { + mutating get { + let currentValue = container.wrappedValue + if currentValue < kMinComposingBufferSize { + return kMinComposingBufferSize + } else if currentValue > kMaxComposingBufferSize { + return kMaxComposingBufferSize + } + return currentValue + } + set { + var value = newValue + if value < kMinComposingBufferSize { + value = kMinComposingBufferSize + } else if value > kMaxComposingBufferSize { + value = kMaxComposingBufferSize + } + container.wrappedValue = value + } + } } // MARK: - @objc enum MandarinParser: Int { - case ofStandard = 0 - case ofEten = 1 - case ofHsu = 2 - case ofEen26 = 3 - case ofIBM = 4 - case ofMiTAC = 5 - case ofFakeSeigyou = 6 - case ofHanyuPinyin = 10 + case ofStandard = 0 + case ofEten = 1 + case ofHsu = 2 + case ofEen26 = 3 + case ofIBM = 4 + case ofMiTAC = 5 + case ofFakeSeigyou = 6 + case ofHanyuPinyin = 10 - var name: String { - switch self { - case .ofStandard: - return "Standard" - case .ofEten: - return "ETen" - case .ofHsu: - return "Hsu" - case .ofEen26: - return "ETen26" - case .ofIBM: - return "IBM" - case .ofMiTAC: - return "MiTAC" - case .ofFakeSeigyou: - return "FakeSeigyou" - case .ofHanyuPinyin: - return "HanyuPinyin" - } - } + var name: String { + switch self { + case .ofStandard: + return "Standard" + case .ofEten: + return "ETen" + case .ofHsu: + return "Hsu" + case .ofEen26: + return "ETen26" + case .ofIBM: + return "IBM" + case .ofMiTAC: + return "MiTAC" + case .ofFakeSeigyou: + return "FakeSeigyou" + case .ofHanyuPinyin: + return "HanyuPinyin" + } + } } // MARK: - public class mgrPrefs: NSObject { - static var allKeys: [String] { - [ - UserDef.kIsDebugModeEnabled, - UserDef.kMostRecentInputMode, - UserDef.kUserDataFolderSpecified, - UserDef.kMandarinParser, - UserDef.kBasicKeyboardLayout, - UserDef.kShowPageButtonsInCandidateWindow, - UserDef.kCandidateListTextSize, - UserDef.kAppleLanguages, - UserDef.kShouldAutoReloadUserDataFiles, - UserDef.kSetRearCursorMode, - UserDef.kUseHorizontalCandidateList, - UserDef.kComposingBufferSize, - UserDef.kChooseCandidateUsingSpace, - UserDef.kCNS11643Enabled, - UserDef.kSymbolInputEnabled, - UserDef.kChineseConversionEnabled, - UserDef.kShiftJISShinjitaiOutputEnabled, - UserDef.kHalfWidthPunctuationEnabled, - UserDef.kSpecifyShiftTabKeyBehavior, - UserDef.kSpecifyShiftSpaceKeyBehavior, - UserDef.kEscToCleanInputBuffer, - UserDef.kCandidateTextFontName, - UserDef.kCandidateKeyLabelFontName, - UserDef.kCandidateKeys, - UserDef.kMoveCursorAfterSelectingCandidate, - UserDef.kPhraseReplacementEnabled, - UserDef.kUseSCPCTypingMode, - UserDef.kMaxCandidateLength, - UserDef.kShouldNotFartInLieuOfBeep, - UserDef.kAssociatedPhrasesEnabled, - ] - } + static var allKeys: [String] { + [ + UserDef.kIsDebugModeEnabled, + UserDef.kMostRecentInputMode, + UserDef.kUserDataFolderSpecified, + UserDef.kMandarinParser, + UserDef.kBasicKeyboardLayout, + UserDef.kShowPageButtonsInCandidateWindow, + UserDef.kCandidateListTextSize, + UserDef.kAppleLanguages, + UserDef.kShouldAutoReloadUserDataFiles, + UserDef.kSetRearCursorMode, + UserDef.kUseHorizontalCandidateList, + UserDef.kComposingBufferSize, + UserDef.kChooseCandidateUsingSpace, + UserDef.kCNS11643Enabled, + UserDef.kSymbolInputEnabled, + UserDef.kChineseConversionEnabled, + UserDef.kShiftJISShinjitaiOutputEnabled, + UserDef.kHalfWidthPunctuationEnabled, + UserDef.kSpecifyShiftTabKeyBehavior, + UserDef.kSpecifyShiftSpaceKeyBehavior, + UserDef.kEscToCleanInputBuffer, + UserDef.kCandidateTextFontName, + UserDef.kCandidateKeyLabelFontName, + UserDef.kCandidateKeys, + UserDef.kMoveCursorAfterSelectingCandidate, + UserDef.kPhraseReplacementEnabled, + UserDef.kUseSCPCTypingMode, + UserDef.kMaxCandidateLength, + UserDef.kShouldNotFartInLieuOfBeep, + UserDef.kAssociatedPhrasesEnabled, + ] + } - // MARK: - 既然 Preferences Module 的預設屬性不自動寫入 plist,那這邊就先寫入了。 + // MARK: - 既然 Preferences Module 的預設屬性不自動寫入 plist,那這邊就先寫入了。 - @objc public static func setMissingDefaults() { - UserDefaults.standard.setDefault(mgrPrefs.isDebugModeEnabled, forKey: UserDef.kIsDebugModeEnabled) - UserDefaults.standard.setDefault(mgrPrefs.mostRecentInputMode, forKey: UserDef.kMostRecentInputMode) - UserDefaults.standard.setDefault(mgrPrefs.checkUpdateAutomatically, forKey: UserDef.kCheckUpdateAutomatically) - UserDefaults.standard.setDefault( - mgrPrefs.showPageButtonsInCandidateWindow, forKey: UserDef.kShowPageButtonsInCandidateWindow - ) - UserDefaults.standard.setDefault(mgrPrefs.symbolInputEnabled, forKey: UserDef.kSymbolInputEnabled) - UserDefaults.standard.setDefault(mgrPrefs.candidateListTextSize, forKey: UserDef.kCandidateListTextSize) - UserDefaults.standard.setDefault(mgrPrefs.chooseCandidateUsingSpace, forKey: UserDef.kChooseCandidateUsingSpace) - UserDefaults.standard.setDefault( - mgrPrefs.shouldAutoReloadUserDataFiles, forKey: UserDef.kShouldAutoReloadUserDataFiles - ) - UserDefaults.standard.setDefault( - mgrPrefs.specifyShiftTabKeyBehavior, forKey: UserDef.kSpecifyShiftTabKeyBehavior - ) - UserDefaults.standard.setDefault( - mgrPrefs.specifyShiftSpaceKeyBehavior, forKey: UserDef.kSpecifyShiftSpaceKeyBehavior - ) - UserDefaults.standard.setDefault(mgrPrefs.useSCPCTypingMode, forKey: UserDef.kUseSCPCTypingMode) - UserDefaults.standard.setDefault(mgrPrefs.associatedPhrasesEnabled, forKey: UserDef.kAssociatedPhrasesEnabled) - UserDefaults.standard.setDefault( - mgrPrefs.setRearCursorMode, forKey: UserDef.kSetRearCursorMode - ) - UserDefaults.standard.setDefault( - mgrPrefs.moveCursorAfterSelectingCandidate, forKey: UserDef.kMoveCursorAfterSelectingCandidate - ) - UserDefaults.standard.setDefault( - mgrPrefs.useHorizontalCandidateList, forKey: UserDef.kUseHorizontalCandidateList - ) - UserDefaults.standard.setDefault(mgrPrefs.cns11643Enabled, forKey: UserDef.kCNS11643Enabled) - UserDefaults.standard.setDefault(mgrPrefs.chineseConversionEnabled, forKey: UserDef.kChineseConversionEnabled) - UserDefaults.standard.setDefault( - mgrPrefs.shiftJISShinjitaiOutputEnabled, forKey: UserDef.kShiftJISShinjitaiOutputEnabled - ) - UserDefaults.standard.setDefault(mgrPrefs.phraseReplacementEnabled, forKey: UserDef.kPhraseReplacementEnabled) - UserDefaults.standard.setDefault(mgrPrefs.shouldNotFartInLieuOfBeep, forKey: UserDef.kShouldNotFartInLieuOfBeep) + public static func setMissingDefaults() { + UserDefaults.standard.setDefault(mgrPrefs.isDebugModeEnabled, forKey: UserDef.kIsDebugModeEnabled) + UserDefaults.standard.setDefault(mgrPrefs.mostRecentInputMode, forKey: UserDef.kMostRecentInputMode) + UserDefaults.standard.setDefault(mgrPrefs.checkUpdateAutomatically, forKey: UserDef.kCheckUpdateAutomatically) + UserDefaults.standard.setDefault( + mgrPrefs.showPageButtonsInCandidateWindow, forKey: UserDef.kShowPageButtonsInCandidateWindow + ) + UserDefaults.standard.setDefault(mgrPrefs.symbolInputEnabled, forKey: UserDef.kSymbolInputEnabled) + UserDefaults.standard.setDefault(mgrPrefs.candidateListTextSize, forKey: UserDef.kCandidateListTextSize) + UserDefaults.standard.setDefault(mgrPrefs.chooseCandidateUsingSpace, forKey: UserDef.kChooseCandidateUsingSpace) + UserDefaults.standard.setDefault( + mgrPrefs.shouldAutoReloadUserDataFiles, forKey: UserDef.kShouldAutoReloadUserDataFiles + ) + UserDefaults.standard.setDefault( + mgrPrefs.specifyShiftTabKeyBehavior, forKey: UserDef.kSpecifyShiftTabKeyBehavior + ) + UserDefaults.standard.setDefault( + mgrPrefs.specifyShiftSpaceKeyBehavior, forKey: UserDef.kSpecifyShiftSpaceKeyBehavior + ) + UserDefaults.standard.setDefault(mgrPrefs.useSCPCTypingMode, forKey: UserDef.kUseSCPCTypingMode) + UserDefaults.standard.setDefault(mgrPrefs.associatedPhrasesEnabled, forKey: UserDef.kAssociatedPhrasesEnabled) + UserDefaults.standard.setDefault( + mgrPrefs.setRearCursorMode, forKey: UserDef.kSetRearCursorMode + ) + UserDefaults.standard.setDefault( + mgrPrefs.moveCursorAfterSelectingCandidate, forKey: UserDef.kMoveCursorAfterSelectingCandidate + ) + UserDefaults.standard.setDefault( + mgrPrefs.useHorizontalCandidateList, forKey: UserDef.kUseHorizontalCandidateList + ) + UserDefaults.standard.setDefault(mgrPrefs.cns11643Enabled, forKey: UserDef.kCNS11643Enabled) + UserDefaults.standard.setDefault(mgrPrefs.chineseConversionEnabled, forKey: UserDef.kChineseConversionEnabled) + UserDefaults.standard.setDefault( + mgrPrefs.shiftJISShinjitaiOutputEnabled, forKey: UserDef.kShiftJISShinjitaiOutputEnabled + ) + UserDefaults.standard.setDefault(mgrPrefs.phraseReplacementEnabled, forKey: UserDef.kPhraseReplacementEnabled) + UserDefaults.standard.setDefault(mgrPrefs.shouldNotFartInLieuOfBeep, forKey: UserDef.kShouldNotFartInLieuOfBeep) - UserDefaults.standard.synchronize() - } + UserDefaults.standard.synchronize() + } - @UserDefault(key: UserDef.kIsDebugModeEnabled, defaultValue: false) - @objc static var isDebugModeEnabled: Bool + @UserDefault(key: UserDef.kIsDebugModeEnabled, defaultValue: false) + static var isDebugModeEnabled: Bool - @UserDefault(key: UserDef.kMostRecentInputMode, defaultValue: "") - @objc static var mostRecentInputMode: String + @UserDefault(key: UserDef.kMostRecentInputMode, defaultValue: "") + static var mostRecentInputMode: String - @UserDefault(key: UserDef.kCheckUpdateAutomatically, defaultValue: false) - @objc static var checkUpdateAutomatically: Bool + @UserDefault(key: UserDef.kCheckUpdateAutomatically, defaultValue: false) + static var checkUpdateAutomatically: Bool - @UserDefault(key: UserDef.kUserDataFolderSpecified, defaultValue: "") - @objc static var userDataFolderSpecified: String + @UserDefault(key: UserDef.kUserDataFolderSpecified, defaultValue: "") + static var userDataFolderSpecified: String - @objc static func ifSpecifiedUserDataPathExistsInPlist() -> Bool { - UserDefaults.standard.object(forKey: UserDef.kUserDataFolderSpecified) != nil - } + static func ifSpecifiedUserDataPathExistsInPlist() -> Bool { + UserDefaults.standard.object(forKey: UserDef.kUserDataFolderSpecified) != nil + } - @objc static func resetSpecifiedUserDataFolder() { - UserDefaults.standard.removeObject(forKey: "UserDataFolderSpecified") - IME.initLangModels(userOnly: true) - } + static func resetSpecifiedUserDataFolder() { + UserDefaults.standard.removeObject(forKey: "UserDataFolderSpecified") + IME.initLangModels(userOnly: true) + } - @UserDefault(key: UserDef.kAppleLanguages, defaultValue: []) - @objc static var appleLanguages: [String] + @UserDefault(key: UserDef.kAppleLanguages, defaultValue: []) + static var appleLanguages: [String] - @UserDefault(key: UserDef.kMandarinParser, defaultValue: 0) - @objc static var mandarinParser: Int + @UserDefault(key: UserDef.kMandarinParser, defaultValue: 0) + @objc static var mandarinParser: Int - @objc static var mandarinParserName: String { - (MandarinParser(rawValue: mandarinParser) ?? MandarinParser.ofStandard).name - } + static var mandarinParserName: String { + (MandarinParser(rawValue: mandarinParser) ?? MandarinParser.ofStandard).name + } - @UserDefault( - key: UserDef.kBasicKeyboardLayout, defaultValue: "com.apple.keylayout.ZhuyinBopomofo" - ) - @objc static var basicKeyboardLayout: String + @UserDefault( + key: UserDef.kBasicKeyboardLayout, defaultValue: "com.apple.keylayout.ZhuyinBopomofo" + ) + static var basicKeyboardLayout: String - @UserDefault(key: UserDef.kShowPageButtonsInCandidateWindow, defaultValue: true) - @objc static var showPageButtonsInCandidateWindow: Bool + @UserDefault(key: UserDef.kShowPageButtonsInCandidateWindow, defaultValue: true) + static var showPageButtonsInCandidateWindow: Bool - @CandidateListTextSize(key: UserDef.kCandidateListTextSize) - @objc static var candidateListTextSize: CGFloat + @CandidateListTextSize(key: UserDef.kCandidateListTextSize) + static var candidateListTextSize: CGFloat - @UserDefault(key: UserDef.kShouldAutoReloadUserDataFiles, defaultValue: true) - @objc static var shouldAutoReloadUserDataFiles: Bool + @UserDefault(key: UserDef.kShouldAutoReloadUserDataFiles, defaultValue: true) + static var shouldAutoReloadUserDataFiles: Bool - @UserDefault(key: UserDef.kSetRearCursorMode, defaultValue: false) - @objc static var setRearCursorMode: Bool + @UserDefault(key: UserDef.kSetRearCursorMode, defaultValue: false) + static var setRearCursorMode: Bool - @UserDefault(key: UserDef.kMoveCursorAfterSelectingCandidate, defaultValue: true) - @objc static var moveCursorAfterSelectingCandidate: Bool + @UserDefault(key: UserDef.kMoveCursorAfterSelectingCandidate, defaultValue: true) + static var moveCursorAfterSelectingCandidate: Bool - @UserDefault(key: UserDef.kUseHorizontalCandidateList, defaultValue: true) - @objc static var useHorizontalCandidateList: Bool + @UserDefault(key: UserDef.kUseHorizontalCandidateList, defaultValue: true) + static var useHorizontalCandidateList: Bool - @ComposingBufferSize(key: UserDef.kComposingBufferSize) - @objc static var composingBufferSize: Int + @ComposingBufferSize(key: UserDef.kComposingBufferSize) + static var composingBufferSize: Int - @UserDefault(key: UserDef.kChooseCandidateUsingSpace, defaultValue: true) - @objc static var chooseCandidateUsingSpace: Bool + @UserDefault(key: UserDef.kChooseCandidateUsingSpace, defaultValue: true) + static var chooseCandidateUsingSpace: Bool - @UserDefault(key: UserDef.kUseSCPCTypingMode, defaultValue: false) - @objc static var useSCPCTypingMode: Bool + @UserDefault(key: UserDef.kUseSCPCTypingMode, defaultValue: false) + static var useSCPCTypingMode: Bool - @objc static func toggleSCPCTypingModeEnabled() -> Bool { - useSCPCTypingMode = !useSCPCTypingMode - UserDefaults.standard.set(useSCPCTypingMode, forKey: UserDef.kUseSCPCTypingMode) - return useSCPCTypingMode - } + static func toggleSCPCTypingModeEnabled() -> Bool { + useSCPCTypingMode = !useSCPCTypingMode + UserDefaults.standard.set(useSCPCTypingMode, forKey: UserDef.kUseSCPCTypingMode) + return useSCPCTypingMode + } - @UserDefault(key: UserDef.kMaxCandidateLength, defaultValue: kDefaultComposingBufferSize * 2) - @objc static var maxCandidateLength: Int + @UserDefault(key: UserDef.kMaxCandidateLength, defaultValue: kDefaultComposingBufferSize * 2) + static var maxCandidateLength: Int - @UserDefault(key: UserDef.kShouldNotFartInLieuOfBeep, defaultValue: true) - @objc static var shouldNotFartInLieuOfBeep: Bool + @UserDefault(key: UserDef.kShouldNotFartInLieuOfBeep, defaultValue: true) + static var shouldNotFartInLieuOfBeep: Bool - @objc static func toggleShouldNotFartInLieuOfBeep() -> Bool { - shouldNotFartInLieuOfBeep = !shouldNotFartInLieuOfBeep - UserDefaults.standard.set(shouldNotFartInLieuOfBeep, forKey: UserDef.kShouldNotFartInLieuOfBeep) - return shouldNotFartInLieuOfBeep - } + static func toggleShouldNotFartInLieuOfBeep() -> Bool { + shouldNotFartInLieuOfBeep = !shouldNotFartInLieuOfBeep + UserDefaults.standard.set(shouldNotFartInLieuOfBeep, forKey: UserDef.kShouldNotFartInLieuOfBeep) + return shouldNotFartInLieuOfBeep + } - @UserDefault(key: UserDef.kCNS11643Enabled, defaultValue: false) - @objc static var cns11643Enabled: Bool + @UserDefault(key: UserDef.kCNS11643Enabled, defaultValue: false) + static var cns11643Enabled: Bool - @objc static func toggleCNS11643Enabled() -> Bool { - cns11643Enabled = !cns11643Enabled - mgrLangModel.setCNSEnabled(cns11643Enabled) // 很重要 - UserDefaults.standard.set(cns11643Enabled, forKey: UserDef.kCNS11643Enabled) - return cns11643Enabled - } + static func toggleCNS11643Enabled() -> Bool { + cns11643Enabled = !cns11643Enabled + mgrLangModel.setCNSEnabled(cns11643Enabled) // 很重要 + UserDefaults.standard.set(cns11643Enabled, forKey: UserDef.kCNS11643Enabled) + return cns11643Enabled + } - @UserDefault(key: UserDef.kSymbolInputEnabled, defaultValue: true) - @objc static var symbolInputEnabled: Bool + @UserDefault(key: UserDef.kSymbolInputEnabled, defaultValue: true) + static var symbolInputEnabled: Bool - @objc static func toggleSymbolInputEnabled() -> Bool { - symbolInputEnabled = !symbolInputEnabled - mgrLangModel.setSymbolEnabled(symbolInputEnabled) // 很重要 - UserDefaults.standard.set(symbolInputEnabled, forKey: UserDef.kSymbolInputEnabled) - return symbolInputEnabled - } + static func toggleSymbolInputEnabled() -> Bool { + symbolInputEnabled = !symbolInputEnabled + mgrLangModel.setSymbolEnabled(symbolInputEnabled) // 很重要 + UserDefaults.standard.set(symbolInputEnabled, forKey: UserDef.kSymbolInputEnabled) + return symbolInputEnabled + } - @UserDefault(key: UserDef.kChineseConversionEnabled, defaultValue: false) - @objc static var chineseConversionEnabled: Bool + @UserDefault(key: UserDef.kChineseConversionEnabled, defaultValue: false) + static var chineseConversionEnabled: Bool - @objc @discardableResult static func toggleChineseConversionEnabled() -> Bool { - chineseConversionEnabled = !chineseConversionEnabled - // 康熙轉換與 JIS 轉換不能同時開啟,否則會出現某些奇奇怪怪的情況 - if chineseConversionEnabled, shiftJISShinjitaiOutputEnabled { - toggleShiftJISShinjitaiOutputEnabled() - UserDefaults.standard.set( - shiftJISShinjitaiOutputEnabled, forKey: UserDef.kShiftJISShinjitaiOutputEnabled - ) - } - UserDefaults.standard.set(chineseConversionEnabled, forKey: UserDef.kChineseConversionEnabled) - return chineseConversionEnabled - } + @discardableResult static func toggleChineseConversionEnabled() -> Bool { + chineseConversionEnabled = !chineseConversionEnabled + // 康熙轉換與 JIS 轉換不能同時開啟,否則會出現某些奇奇怪怪的情況 + if chineseConversionEnabled, shiftJISShinjitaiOutputEnabled { + toggleShiftJISShinjitaiOutputEnabled() + UserDefaults.standard.set( + shiftJISShinjitaiOutputEnabled, forKey: UserDef.kShiftJISShinjitaiOutputEnabled + ) + } + UserDefaults.standard.set(chineseConversionEnabled, forKey: UserDef.kChineseConversionEnabled) + return chineseConversionEnabled + } - @UserDefault(key: UserDef.kShiftJISShinjitaiOutputEnabled, defaultValue: false) - @objc static var shiftJISShinjitaiOutputEnabled: Bool + @UserDefault(key: UserDef.kShiftJISShinjitaiOutputEnabled, defaultValue: false) + static var shiftJISShinjitaiOutputEnabled: Bool - @objc @discardableResult static func toggleShiftJISShinjitaiOutputEnabled() -> Bool { - shiftJISShinjitaiOutputEnabled = !shiftJISShinjitaiOutputEnabled - // 康熙轉換與 JIS 轉換不能同時開啟,否則會出現某些奇奇怪怪的情況 - if shiftJISShinjitaiOutputEnabled, chineseConversionEnabled { - toggleChineseConversionEnabled() - } - UserDefaults.standard.set( - shiftJISShinjitaiOutputEnabled, forKey: UserDef.kShiftJISShinjitaiOutputEnabled - ) - return shiftJISShinjitaiOutputEnabled - } + @discardableResult static func toggleShiftJISShinjitaiOutputEnabled() -> Bool { + shiftJISShinjitaiOutputEnabled = !shiftJISShinjitaiOutputEnabled + // 康熙轉換與 JIS 轉換不能同時開啟,否則會出現某些奇奇怪怪的情況 + if shiftJISShinjitaiOutputEnabled, chineseConversionEnabled { + toggleChineseConversionEnabled() + } + UserDefaults.standard.set( + shiftJISShinjitaiOutputEnabled, forKey: UserDef.kShiftJISShinjitaiOutputEnabled + ) + return shiftJISShinjitaiOutputEnabled + } - @UserDefault(key: UserDef.kHalfWidthPunctuationEnabled, defaultValue: false) - @objc static var halfWidthPunctuationEnabled: Bool + @UserDefault(key: UserDef.kHalfWidthPunctuationEnabled, defaultValue: false) + static var halfWidthPunctuationEnabled: Bool - @objc static func toggleHalfWidthPunctuationEnabled() -> Bool { - halfWidthPunctuationEnabled = !halfWidthPunctuationEnabled - return halfWidthPunctuationEnabled - } + static func toggleHalfWidthPunctuationEnabled() -> Bool { + halfWidthPunctuationEnabled = !halfWidthPunctuationEnabled + return halfWidthPunctuationEnabled + } - @UserDefault(key: UserDef.kEscToCleanInputBuffer, defaultValue: true) - @objc static var escToCleanInputBuffer: Bool + @UserDefault(key: UserDef.kEscToCleanInputBuffer, defaultValue: true) + static var escToCleanInputBuffer: Bool - @UserDefault(key: UserDef.kSpecifyShiftTabKeyBehavior, defaultValue: false) - @objc static var specifyShiftTabKeyBehavior: Bool + @UserDefault(key: UserDef.kSpecifyShiftTabKeyBehavior, defaultValue: false) + static var specifyShiftTabKeyBehavior: Bool - @UserDefault(key: UserDef.kSpecifyShiftSpaceKeyBehavior, defaultValue: false) - @objc static var specifyShiftSpaceKeyBehavior: Bool + @UserDefault(key: UserDef.kSpecifyShiftSpaceKeyBehavior, defaultValue: false) + static var specifyShiftSpaceKeyBehavior: Bool - // MARK: - Optional settings + // MARK: - Optional settings - @UserDefault(key: UserDef.kCandidateTextFontName, defaultValue: nil) - @objc static var candidateTextFontName: String? + @UserDefault(key: UserDef.kCandidateTextFontName, defaultValue: nil) + static var candidateTextFontName: String? - @UserDefault(key: UserDef.kCandidateKeyLabelFontName, defaultValue: nil) - @objc static var candidateKeyLabelFontName: String? + @UserDefault(key: UserDef.kCandidateKeyLabelFontName, defaultValue: nil) + static var candidateKeyLabelFontName: String? - @UserDefault(key: UserDef.kCandidateKeys, defaultValue: kDefaultKeys) - @objc static var candidateKeys: String + @UserDefault(key: UserDef.kCandidateKeys, defaultValue: kDefaultKeys) + static var candidateKeys: String - @objc static var defaultCandidateKeys: String { - kDefaultKeys - } + static var defaultCandidateKeys: String { + kDefaultKeys + } - @objc static var suggestedCandidateKeys: [String] { - [kDefaultKeys, "234567890", "QWERTYUIO", "QWERTASDF", "ASDFGHJKL", "ASDFZXCVB"] - } + static var suggestedCandidateKeys: [String] { + [kDefaultKeys, "234567890", "QWERTYUIO", "QWERTASDF", "ASDFGHJKL", "ASDFZXCVB"] + } - @objc static func validate(candidateKeys: String) throws { - let trimmed = candidateKeys.trimmingCharacters(in: .whitespacesAndNewlines) - if trimmed.isEmpty { - throw CandidateKeyError.empty - } - if !trimmed.canBeConverted(to: .ascii) { - throw CandidateKeyError.invalidCharacters - } - if trimmed.contains(" ") { - throw CandidateKeyError.containSpace - } - if trimmed.count < 4 { - throw CandidateKeyError.tooShort - } - if trimmed.count > 15 { - throw CandidateKeyError.tooLong - } - let set = Set(Array(trimmed)) - if set.count != trimmed.count { - throw CandidateKeyError.duplicatedCharacters - } - } + static func validate(candidateKeys: String) throws { + let trimmed = candidateKeys.trimmingCharacters(in: .whitespacesAndNewlines) + if trimmed.isEmpty { + throw CandidateKeyError.empty + } + if !trimmed.canBeConverted(to: .ascii) { + throw CandidateKeyError.invalidCharacters + } + if trimmed.contains(" ") { + throw CandidateKeyError.containSpace + } + if trimmed.count < 4 { + throw CandidateKeyError.tooShort + } + if trimmed.count > 15 { + throw CandidateKeyError.tooLong + } + let set = Set(Array(trimmed)) + if set.count != trimmed.count { + throw CandidateKeyError.duplicatedCharacters + } + } - enum CandidateKeyError: Error, LocalizedError { - case empty - case invalidCharacters - case containSpace - case duplicatedCharacters - case tooShort - case tooLong + enum CandidateKeyError: Error, LocalizedError { + case empty + case invalidCharacters + case containSpace + case duplicatedCharacters + case tooShort + case tooLong - var errorDescription: String? { - switch self { - case .empty: - return NSLocalizedString("Candidates keys cannot be empty.", comment: "") - case .invalidCharacters: - return NSLocalizedString( - "Candidate keys can only contain ASCII characters like alphanumericals.", - comment: "" - ) - case .containSpace: - return NSLocalizedString("Candidate keys cannot contain space.", comment: "") - case .duplicatedCharacters: - return NSLocalizedString("There should not be duplicated keys.", comment: "") - case .tooShort: - return NSLocalizedString( - "Please specify at least 4 candidate keys.", comment: "" - ) - case .tooLong: - return NSLocalizedString("Maximum 15 candidate keys allowed.", comment: "") - } - } - } + var errorDescription: String? { + switch self { + case .empty: + return NSLocalizedString("Candidates keys cannot be empty.", comment: "") + case .invalidCharacters: + return NSLocalizedString( + "Candidate keys can only contain ASCII characters like alphanumericals.", + comment: "" + ) + case .containSpace: + return NSLocalizedString("Candidate keys cannot contain space.", comment: "") + case .duplicatedCharacters: + return NSLocalizedString("There should not be duplicated keys.", comment: "") + case .tooShort: + return NSLocalizedString( + "Please specify at least 4 candidate keys.", comment: "" + ) + case .tooLong: + return NSLocalizedString("Maximum 15 candidate keys allowed.", comment: "") + } + } + } - @UserDefault(key: UserDef.kPhraseReplacementEnabled, defaultValue: false) - @objc static var phraseReplacementEnabled: Bool + @UserDefault(key: UserDef.kPhraseReplacementEnabled, defaultValue: false) + static var phraseReplacementEnabled: Bool - @objc static func togglePhraseReplacementEnabled() -> Bool { - phraseReplacementEnabled = !phraseReplacementEnabled - mgrLangModel.setPhraseReplacementEnabled(phraseReplacementEnabled) - UserDefaults.standard.set(phraseReplacementEnabled, forKey: UserDef.kPhraseReplacementEnabled) - return phraseReplacementEnabled - } + static func togglePhraseReplacementEnabled() -> Bool { + phraseReplacementEnabled = !phraseReplacementEnabled + mgrLangModel.setPhraseReplacementEnabled(phraseReplacementEnabled) + UserDefaults.standard.set(phraseReplacementEnabled, forKey: UserDef.kPhraseReplacementEnabled) + return phraseReplacementEnabled + } - @UserDefault(key: UserDef.kAssociatedPhrasesEnabled, defaultValue: false) - @objc static var associatedPhrasesEnabled: Bool + @UserDefault(key: UserDef.kAssociatedPhrasesEnabled, defaultValue: false) + static var associatedPhrasesEnabled: Bool - @objc static func toggleAssociatedPhrasesEnabled() -> Bool { - associatedPhrasesEnabled = !associatedPhrasesEnabled - UserDefaults.standard.set(associatedPhrasesEnabled, forKey: UserDef.kAssociatedPhrasesEnabled) - return associatedPhrasesEnabled - } + static func toggleAssociatedPhrasesEnabled() -> Bool { + associatedPhrasesEnabled = !associatedPhrasesEnabled + UserDefaults.standard.set(associatedPhrasesEnabled, forKey: UserDef.kAssociatedPhrasesEnabled) + return associatedPhrasesEnabled + } } diff --git a/Source/Modules/LangModelRelated/LMConsolidator.swift b/Source/Modules/LangModelRelated/LMConsolidator.swift new file mode 100644 index 00000000..30f173dc --- /dev/null +++ b/Source/Modules/LangModelRelated/LMConsolidator.swift @@ -0,0 +1,167 @@ +// Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License). +/* +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +import Foundation + +extension vChewing { + public enum LMConsolidator { + public static let kPragmaHeader = "# 𝙵𝙾𝚁𝙼𝙰𝚃 𝚘𝚛𝚐.𝚊𝚝𝚎𝚕𝚒𝚎𝚛𝙸𝚗𝚖𝚞.𝚟𝚌𝚑𝚎𝚠𝚒𝚗𝚐.𝚞𝚜𝚎𝚛𝙻𝚊𝚗𝚐𝚞𝚊𝚐𝚎𝙼𝚘𝚍𝚎𝚕𝙳𝚊𝚝𝚊.𝚏𝚘𝚛𝚖𝚊𝚝𝚝𝚎𝚍" + + public static func checkPragma(path: String) -> Bool { + if FileManager.default.fileExists(atPath: path) { + let fileHandle = FileHandle(forReadingAtPath: path)! + do { + let lineReader = try LineReader(file: fileHandle) + for strLine in lineReader { // 不需要 i=0,因為第一遍迴圈就出結果。 + if strLine != kPragmaHeader { + IME.prtDebugIntel("Header Mismatch, Starting In-Place Consolidation.") + return false + } else { + IME.prtDebugIntel("Header Verification Succeeded: \(strLine).") + return true + } + } + } catch { + IME.prtDebugIntel("Header Verification Failed: File Access Error.") + return false + } + } + IME.prtDebugIntel("Header Verification Failed: File Missing.") + return false + } + + @discardableResult public static func fixEOF(path: String) -> Bool { + let urlPath = URL(fileURLWithPath: path) + if FileManager.default.fileExists(atPath: path) { + var strIncoming = "" + do { + strIncoming += try String(contentsOf: urlPath, encoding: .utf8) + if !strIncoming.hasSuffix("\n") { + IME.prtDebugIntel("EOF Fix Necessity Confirmed, Start Fixing.") + if let writeFile = FileHandle(forUpdatingAtPath: path), + let endl = "\n".data(using: .utf8) + { + writeFile.seekToEndOfFile() + writeFile.write(endl) + writeFile.closeFile() + } else { + return false + } + } + } catch { + IME.prtDebugIntel("EOF Fix Failed w/ File: \(path)") + IME.prtDebugIntel("EOF Fix Failed w/ Error: \(error).") + return false + } + IME.prtDebugIntel("EOF Successfully Ensured (with possible autofixes performed).") + return true + } + IME.prtDebugIntel("EOF Fix Failed: File Missing at \(path).") + return false + } + + @discardableResult public static func consolidate(path: String, pragma shouldCheckPragma: Bool) -> Bool { + var pragmaResult = false + if shouldCheckPragma { + pragmaResult = checkPragma(path: path) + if pragmaResult { + return true + } + } + + let urlPath = URL(fileURLWithPath: path) + if FileManager.default.fileExists(atPath: path) { + var strProcessed = "" + do { + strProcessed += try String(contentsOf: urlPath, encoding: .utf8) + + // Step 1: Consolidating formats per line. + // ------- + // CJKWhiteSpace (\x{3000}) to ASCII Space + // NonBreakWhiteSpace (\x{A0}) to ASCII Space + // Tab to ASCII Space + // 統整連續空格為一個 ASCII 空格 + strProcessed.regReplace(pattern: #"( +| +| +|\t+)+"#, replaceWith: " ") + // 去除行尾行首空格 + strProcessed.regReplace(pattern: #"(^ | $)"#, replaceWith: "") + // CR & FF to LF, 且去除重複行 + strProcessed.regReplace(pattern: #"(\f+|\r+|\n+)+"#, replaceWith: "\n") + if strProcessed.prefix(1) == " " { // 去除檔案開頭空格 + strProcessed.removeFirst() + } + if strProcessed.suffix(1) == " " { // 去除檔案結尾空格 + strProcessed.removeLast() + } + + // Step 3: Add Formatted Pragma, the Sorted Header: + if !pragmaResult { + strProcessed = kPragmaHeader + "\n" + strProcessed // Add Sorted Header + } + + // Step 4: Deduplication. + let arrData = strProcessed.components(separatedBy: "\n") + strProcessed = "" // Reset its value + // 下面兩行的 reversed 是首尾顛倒,免得破壞最新的 override 資訊。 + let arrDataDeduplicated = Array(NSOrderedSet(array: arrData.reversed()).array as! [String]) + for lineData in arrDataDeduplicated.reversed() { + strProcessed += lineData + strProcessed += "\n" + } + + // Step 5: Remove duplicated newlines at the end of the file. + strProcessed.regReplace(pattern: "\\n+", replaceWith: "\n") + + // Step 6: Write consolidated file contents. + try strProcessed.write(to: urlPath, atomically: false, encoding: .utf8) + + } catch { + IME.prtDebugIntel("Consolidation Failed w/ File: \(path)") + IME.prtDebugIntel("Consolidation Failed w/ Error: \(error).") + return false + } + IME.prtDebugIntel("Either Consolidation Successful Or No-Need-To-Consolidate.") + return true + } + IME.prtDebugIntel("Consolidation Failed: File Missing at \(path).") + return false + } + } +} + +// MARK: - String Extension + +extension String { + fileprivate mutating func regReplace(pattern: String, replaceWith: String = "") { + // Ref: https://stackoverflow.com/a/40993403/4162914 && https://stackoverflow.com/a/71291137/4162914 + do { + let regex = try NSRegularExpression( + pattern: pattern, options: [.caseInsensitive, .anchorsMatchLines] + ) + let range = NSRange(startIndex..., in: self) + self = regex.stringByReplacingMatches( + in: self, options: [], range: range, withTemplate: replaceWith + ) + } catch { return } + } +} diff --git a/Source/Modules/LangModelRelated/LMInstantiator.h b/Source/Modules/LangModelRelated/LMInstantiator.h deleted file mode 100644 index fdbf92a7..00000000 --- a/Source/Modules/LangModelRelated/LMInstantiator.h +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are of: -// (c) 2021 and onwards The vChewing Project (MIT-NTL License). -/* -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -1. The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -2. No trademark license is granted to use the trade names, trademarks, service -marks, or product names of Contributor, except as required to fulfill notice -requirements above. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -#ifndef LMInstantiator_H -#define LMInstantiator_H - -#include "AssociatedPhrases.h" -#include "CNSLM.h" -#include "CoreLM.h" -#include "ParselessLM.h" -#include "PhraseReplacementMap.h" -#include "SymbolLM.h" -#include "UserPhrasesLM.h" -#include "UserSymbolLM.h" -#include -#include - -namespace vChewing -{ - -using namespace Gramambular; - -/// LMInstantiator is a facade for managing a set of models including -/// the input method language model, user phrases and excluded phrases. -/// -/// It is the primary model class that the input controller and grammar builder -/// of vChewing talks to. When the grammar builder starts to build a sentence -/// from a series of BPMF readings, it passes the readings to the model to see -/// if there are valid unigrams, and use returned unigrams to produce the final -/// results. -/// -/// LMInstantiator combine and transform the unigrams from the primary language -/// model and user phrases. The process is -/// -/// 1) Get the original unigrams. -/// 2) Drop the unigrams whose value is contained in the exclusion map. -/// 3) Replace the values of the unigrams using the phrase replacement map. -/// 4) Replace the values of the unigrams using an external converter lambda. -/// 5) Drop the duplicated phrases. -/// -/// The controller can ask the model to load the primary input method language -/// model while launching and to load the user phrases anytime if the custom -/// files are modified. It does not keep the reference of the data pathes but -/// you have to pass the paths when you ask it to do loading. -class LMInstantiator : public Gramambular::LanguageModel -{ - public: - LMInstantiator(); - ~LMInstantiator(); - - /// Asks to load the primary language model at the given path. - /// @param languageModelPath The path of the language model. - void loadLanguageModel(const char *languageModelPath); - /// If the data model is already loaded. - bool isDataModelLoaded(); - - /// Asks to load the primary language model at the given path. - /// @param miscDataPath The path of the misc data model. - void loadMiscData(const char *miscDataPath); - /// If the data model is already loaded. - bool isMiscDataLoaded(); - - /// Asks to load the primary language model at the given path. - /// @param symbolDataPath The path of the symbol data model. - void loadSymbolData(const char *symbolDataPath); - /// If the data model is already loaded. - bool isSymbolDataLoaded(); - - /// Asks to load the primary language model at the given path. - /// @param cnsDataPath The path of the CNS data model. - void loadCNSData(const char *cnsDataPath); - /// If the data model is already loaded. - bool isCNSDataLoaded(); - - /// Asks to load the user phrases and excluded phrases at the given path. - /// @param userPhrasesPath The path of user phrases. - /// @param excludedPhrasesPath The path of excluded phrases. - void loadUserPhrases(const char *userPhrasesPath, const char *excludedPhrasesPath); - /// Asks to load the user symbol data at the given path. - /// @param userSymbolDataPath The path of user symbol data. - void loadUserSymbolData(const char *userPhrasesPath); - /// Asks to load the user associated phrases at the given path. - /// @param userAssociatedPhrasesPath The path of the user associated phrases. - void loadUserAssociatedPhrases(const char *userAssociatedPhrasesPath); - /// Asks to load the phrase replacement table at the given path. - /// @param phraseReplacementPath The path of the phrase replacement table. - void loadPhraseReplacementMap(const char *phraseReplacementPath); - - /// Not implemented since we do not have data to provide bigram function. - const std::vector bigramsForKeys(const std::string &preceedingKey, const std::string &key); - /// Returns a list of available unigram for the given key. - /// @param key A std::string represents the BPMF reading or a symbol key. For - /// example, it you pass "ㄇㄚ", it returns "嗎", "媽", and so on. - const std::vector unigramsForKey(const std::string &key); - /// If the model has unigrams for the given key. - /// @param key The key. - bool hasUnigramsForKey(const std::string &key); - - /// Enables or disables phrase replacement. - void setPhraseReplacementEnabled(bool enabled); - /// If phrase replacement is enabled or not. - bool phraseReplacementEnabled(); - - /// Enables or disables symbol input. - void setSymbolEnabled(bool enabled); - /// If symbol input is enabled or not. - bool symbolEnabled(); - - /// Enables or disables CNS11643 input. - void setCNSEnabled(bool enabled); - /// If CNS11643 input is enabled or not. - bool cnsEnabled(); - - const std::vector associatedPhrasesForKey(const std::string &key); - bool hasAssociatedPhrasesForKey(const std::string &key); - - protected: - /// Filters and converts the input unigrams and return a new list of unigrams. - /// - /// @param unigrams The unigrams to be processed. - /// @param excludedValues The values to excluded unigrams. - /// @param insertedValues The values for unigrams already in the results. - /// It helps to prevent duplicated unigrams. Please note that the method - /// has a side effect that it inserts values to `insertedValues`. - const std::vector filterAndTransformUnigrams( - const std::vector unigrams, const std::unordered_set &excludedValues, - std::unordered_set &insertedValues); - - ParselessLM m_languageModel; - CoreLM m_miscModel; - SymbolLM m_symbolModel; - CNSLM m_cnsModel; - UserPhrasesLM m_userPhrases; - UserPhrasesLM m_excludedPhrases; - UserSymbolLM m_userSymbolModel; - PhraseReplacementMap m_phraseReplacement; - AssociatedPhrases m_associatedPhrases; - bool m_phraseReplacementEnabled; - bool m_cnsEnabled; - bool m_symbolEnabled; -}; -}; // namespace vChewing - -#endif diff --git a/Source/Modules/LangModelRelated/LMInstantiator.mm b/Source/Modules/LangModelRelated/LMInstantiator.mm deleted file mode 100644 index 2873cbf2..00000000 --- a/Source/Modules/LangModelRelated/LMInstantiator.mm +++ /dev/null @@ -1,323 +0,0 @@ -// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are of: -// (c) 2021 and onwards The vChewing Project (MIT-NTL License). -/* -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -1. The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -2. No trademark license is granted to use the trade names, trademarks, service -marks, or product names of Contributor, except as required to fulfill notice -requirements above. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -#include "LMInstantiator.h" -#include -#include - -namespace vChewing -{ - -LMInstantiator::LMInstantiator() -{ -} - -LMInstantiator::~LMInstantiator() -{ - m_languageModel.close(); - m_miscModel.close(); - m_userPhrases.close(); - m_userSymbolModel.close(); - m_cnsModel.close(); - m_excludedPhrases.close(); - m_phraseReplacement.close(); - m_associatedPhrases.close(); -} - -void LMInstantiator::loadLanguageModel(const char *languageModelDataPath) -{ - if (languageModelDataPath) - { - m_languageModel.close(); - m_languageModel.open(languageModelDataPath); - } -} - -bool LMInstantiator::isDataModelLoaded() -{ - return m_languageModel.isLoaded(); -} - -void LMInstantiator::loadCNSData(const char *cnsDataPath) -{ - if (cnsDataPath) - { - m_cnsModel.close(); - m_cnsModel.open(cnsDataPath); - } -} - -bool LMInstantiator::isCNSDataLoaded() -{ - return m_cnsModel.isLoaded(); -} - -void LMInstantiator::loadMiscData(const char *miscDataPath) -{ - if (miscDataPath) - { - m_miscModel.close(); - m_miscModel.open(miscDataPath); - } -} - -bool LMInstantiator::isMiscDataLoaded() -{ - return m_miscModel.isLoaded(); -} - -void LMInstantiator::loadSymbolData(const char *symbolDataPath) -{ - if (symbolDataPath) - { - m_symbolModel.close(); - m_symbolModel.open(symbolDataPath); - } -} - -bool LMInstantiator::isSymbolDataLoaded() -{ - return m_symbolModel.isLoaded(); -} - -void LMInstantiator::loadUserPhrases(const char *userPhrasesDataPath, const char *excludedPhrasesDataPath) -{ - if (userPhrasesDataPath) - { - m_userPhrases.close(); - m_userPhrases.open(userPhrasesDataPath); - } - if (excludedPhrasesDataPath) - { - m_excludedPhrases.close(); - m_excludedPhrases.open(excludedPhrasesDataPath); - } -} - -void LMInstantiator::loadUserSymbolData(const char *userSymbolDataPath) -{ - if (userSymbolDataPath) - { - m_userSymbolModel.close(); - m_userSymbolModel.open(userSymbolDataPath); - } -} - -void LMInstantiator::loadUserAssociatedPhrases(const char *userAssociatedPhrasesPath) -{ - if (userAssociatedPhrasesPath) - { - m_associatedPhrases.close(); - m_associatedPhrases.open(userAssociatedPhrasesPath); - } -} - -void LMInstantiator::loadPhraseReplacementMap(const char *phraseReplacementPath) -{ - if (phraseReplacementPath) - { - m_phraseReplacement.close(); - m_phraseReplacement.open(phraseReplacementPath); - } -} - -const std::vector LMInstantiator::bigramsForKeys(const std::string &preceedingKey, - const std::string &key) -{ - return std::vector(); -} - -const std::vector LMInstantiator::unigramsForKey(const std::string &key) -{ - if (key == " ") - { - std::vector spaceUnigrams; - Gramambular::Unigram g; - g.keyValue.key = " "; - g.keyValue.value = " "; - g.score = 0; - spaceUnigrams.push_back(g); - return spaceUnigrams; - } - - std::vector allUnigrams; - std::vector miscUnigrams; - std::vector symbolUnigrams; - std::vector userUnigrams; - std::vector userSymbolUnigrams; - std::vector cnsUnigrams; - - std::unordered_set excludedValues; - std::unordered_set insertedValues; - - if (m_excludedPhrases.hasUnigramsForKey(key)) - { - std::vector excludedUnigrams = m_excludedPhrases.unigramsForKey(key); - transform(excludedUnigrams.begin(), excludedUnigrams.end(), inserter(excludedValues, excludedValues.end()), - [](const Gramambular::Unigram &u) { return u.keyValue.value; }); - } - - if (m_userPhrases.hasUnigramsForKey(key)) - { - std::vector rawUserUnigrams = m_userPhrases.unigramsForKey(key); - // 用這句指令讓使用者語彙檔案內的詞條優先順序隨著行數增加而逐漸增高。 - // 這樣一來就可以在就地新增語彙時徹底複寫優先權。 - std::reverse(rawUserUnigrams.begin(), rawUserUnigrams.end()); - userUnigrams = filterAndTransformUnigrams(rawUserUnigrams, excludedValues, insertedValues); - } - - if (m_languageModel.hasUnigramsForKey(key)) - { - std::vector rawGlobalUnigrams = m_languageModel.unigramsForKey(key); - allUnigrams = filterAndTransformUnigrams(rawGlobalUnigrams, excludedValues, insertedValues); - } - - if (m_miscModel.hasUnigramsForKey(key)) - { - std::vector rawMiscUnigrams = m_miscModel.unigramsForKey(key); - miscUnigrams = filterAndTransformUnigrams(rawMiscUnigrams, excludedValues, insertedValues); - } - - if (m_symbolModel.hasUnigramsForKey(key) && m_symbolEnabled) - { - std::vector rawSymbolUnigrams = m_symbolModel.unigramsForKey(key); - symbolUnigrams = filterAndTransformUnigrams(rawSymbolUnigrams, excludedValues, insertedValues); - } - - if (m_userSymbolModel.hasUnigramsForKey(key) && m_symbolEnabled) - { - std::vector rawUserSymbolUnigrams = m_userSymbolModel.unigramsForKey(key); - userSymbolUnigrams = filterAndTransformUnigrams(rawUserSymbolUnigrams, excludedValues, insertedValues); - } - - if (m_cnsModel.hasUnigramsForKey(key) && m_cnsEnabled) - { - std::vector rawCNSUnigrams = m_cnsModel.unigramsForKey(key); - cnsUnigrams = filterAndTransformUnigrams(rawCNSUnigrams, excludedValues, insertedValues); - } - - allUnigrams.insert(allUnigrams.begin(), userUnigrams.begin(), userUnigrams.end()); - allUnigrams.insert(allUnigrams.end(), cnsUnigrams.begin(), cnsUnigrams.end()); - allUnigrams.insert(allUnigrams.begin(), miscUnigrams.begin(), miscUnigrams.end()); - allUnigrams.insert(allUnigrams.end(), userSymbolUnigrams.begin(), userSymbolUnigrams.end()); - allUnigrams.insert(allUnigrams.end(), symbolUnigrams.begin(), symbolUnigrams.end()); - return allUnigrams; -} - -bool LMInstantiator::hasUnigramsForKey(const std::string &key) -{ - if (key == " ") - { - return true; - } - - if (!m_excludedPhrases.hasUnigramsForKey(key)) - { - return m_userPhrases.hasUnigramsForKey(key) || m_languageModel.hasUnigramsForKey(key); - } - - return unigramsForKey(key).size() > 0; -} - -void LMInstantiator::setPhraseReplacementEnabled(bool enabled) -{ - m_phraseReplacementEnabled = enabled; -} - -bool LMInstantiator::phraseReplacementEnabled() -{ - return m_phraseReplacementEnabled; -} - -void LMInstantiator::setCNSEnabled(bool enabled) -{ - m_cnsEnabled = enabled; -} - -bool LMInstantiator::cnsEnabled() -{ - return m_cnsEnabled; -} - -void LMInstantiator::setSymbolEnabled(bool enabled) -{ - m_symbolEnabled = enabled; -} - -bool LMInstantiator::symbolEnabled() -{ - return m_symbolEnabled; -} - -const std::vector LMInstantiator::filterAndTransformUnigrams( - const std::vector unigrams, const std::unordered_set &excludedValues, - std::unordered_set &insertedValues) -{ - std::vector results; - - for (auto &&unigram : unigrams) - { - // excludedValues filters out the unigrams with the original value. - // insertedValues filters out the ones with the converted value - std::string originalValue = unigram.keyValue.value; - if (excludedValues.find(originalValue) != excludedValues.end()) - { - continue; - } - - std::string value = originalValue; - if (m_phraseReplacementEnabled) - { - std::string replacement = m_phraseReplacement.valueForKey(value); - if (replacement != "") - { - value = replacement; - } - } - if (insertedValues.find(value) == insertedValues.end()) - { - Gramambular::Unigram g; - g.keyValue.value = value; - g.keyValue.key = unigram.keyValue.key; - g.score = unigram.score; - results.push_back(g); - insertedValues.insert(value); - } - } - return results; -} - -const std::vector LMInstantiator::associatedPhrasesForKey(const std::string &key) -{ - return m_associatedPhrases.valuesForKey(key); -} - -bool LMInstantiator::hasAssociatedPhrasesForKey(const std::string &key) -{ - return m_associatedPhrases.hasValuesForKey(key); -} - -} // namespace vChewing diff --git a/Source/Modules/LangModelRelated/LMInstantiator.swift b/Source/Modules/LangModelRelated/LMInstantiator.swift new file mode 100644 index 00000000..c075e5cd --- /dev/null +++ b/Source/Modules/LangModelRelated/LMInstantiator.swift @@ -0,0 +1,301 @@ +// Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// Refactored from the ObjCpp-version of this class by: +// (c) 2011 and onwards The OpenVanilla Project (MIT License). +/* +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +// NOTE: We still keep some of the comments left by Zonble, +// regardless that he is not in charge of this Swift module。 + +import Foundation + +// 簡體中文模式與繁體中文模式共用全字庫擴展模組,故單獨處理。 +// 塞在 LMInstantiator 內的話,每個模式都會讀入一份全字庫,會多佔用 100MB 記憶體。 +private var lmCNS = vChewing.LMLite(consolidate: false) +private var lmSymbols = vChewing.LMCore(reverse: true, consolidate: false, defaultScore: -13.0, forceDefaultScore: true) + +extension vChewing { + /// LMInstantiator is a facade for managing a set of models including + /// the input method language model, user phrases and excluded phrases. + /// + /// It is the primary model class that the input controller and grammar builder + /// of vChewing talks to. When the grammar builder starts to build a sentence + /// from a series of BPMF readings, it passes the readings to the model to see + /// if there are valid unigrams, and use returned unigrams to produce the final + /// results. + /// + /// LMInstantiator combine and transform the unigrams from the primary language + /// model and user phrases. The process is + /// + /// 1) Get the original unigrams. + /// 2) Drop the unigrams whose value is contained in the exclusion map. + /// 3) Replace the values of the unigrams using the phrase replacement map. + /// 4) Drop the duplicated phrases from the generated unigram array. + /// + /// The controller can ask the model to load the primary input method language + /// model while launching and to load the user phrases anytime if the custom + /// files are modified. It does not keep the reference of the data pathes but + /// you have to pass the paths when you ask it to load. + public class LMInstantiator: Megrez.LanguageModel { + // 在函數內部用以記錄狀態的開關。 + public var isPhraseReplacementEnabled = false + public var isCNSEnabled = false + public var isSymbolEnabled = false + + /// 介紹一下三個通用的語言模組型別: + /// LMCore 是全功能通用型的模組,每一筆辭典記錄以 key 為注音、以 [Unigram] 陣列作為記錄內容。 + /// 比較適合那種每筆記錄都有不同的權重數值的語言模組,雖然也可以強制施加權重數值就是了。 + /// 然而缺點是:哪怕你強制施加權重數值,也不會減輕記憶體佔用。 + /// 至於像全字庫這樣所有記錄都使用同一權重數值的模組,可以用 LMLite 以節省記憶體佔用。 + /// LMLite 的辭典內不會存儲權重資料,只會在每次讀取記錄時施加您給定的權重數值。 + /// LMLite 與 LMCore 都會用到多執行緒、以加速載入(不然的話,全部資料載入會耗費八秒左右)。 + /// LMReplacements 與 LMAssociates 均為特種模組,分別擔當語彙置換表資料與使用者聯想詞的資料承載工作。 + + // 聲明原廠語言模組 + /// Reverse 的話,第一欄是注音,第二欄是對應的漢字,第三欄是可能的權重。 + /// 不 Reverse 的話,第一欄是漢字,第二欄是對應的注音,第三欄是可能的權重。 + var lmCore = LMCore(reverse: false, consolidate: false, defaultScore: -9.5, forceDefaultScore: false) + var lmMisc = LMCore(reverse: true, consolidate: false, defaultScore: -1, forceDefaultScore: false) + + // 聲明使用者語言模組。 + // 使用者語言模組使用多執行緒的話,可能會導致一些問題。有時間再仔細排查看看。 + var lmUserPhrases = LMLite(consolidate: true) + var lmFiltered = LMLite(consolidate: true) + var lmUserSymbols = LMLite(consolidate: true) + var lmReplacements = LMReplacments() + var lmAssociates = LMAssociates() + + // 初期化的函數先保留 + override init() {} + + // 以下這些函數命名暫時保持原樣,等弒神行動徹底結束了再調整。 + + public func isDataModelLoaded() -> Bool { lmCore.isLoaded() } + public func loadLanguageModel(path: String) { + if FileManager.default.isReadableFile(atPath: path) { + lmCore.open(path) + IME.prtDebugIntel("lmCore: \(lmCore.count) entries of data loaded from: \(path)") + } else { + IME.prtDebugIntel("lmCore: File access failure: \(path)") + } + } + + public func isCNSDataLoaded() -> Bool { lmCNS.isLoaded() } + public func loadCNSData(path: String) { + if FileManager.default.isReadableFile(atPath: path) { + lmCNS.open(path) + IME.prtDebugIntel("lmCNS: \(lmCNS.count) entries of data loaded from: \(path)") + } else { + IME.prtDebugIntel("lmCNS: File access failure: \(path)") + } + } + + public func isMiscDataLoaded() -> Bool { lmMisc.isLoaded() } + public func loadMiscData(path: String) { + if FileManager.default.isReadableFile(atPath: path) { + lmMisc.open(path) + IME.prtDebugIntel("lmMisc: \(lmMisc.count) entries of data loaded from: \(path)") + } else { + IME.prtDebugIntel("lmMisc: File access failure: \(path)") + } + } + + public func isSymbolDataLoaded() -> Bool { lmSymbols.isLoaded() } + public func loadSymbolData(path: String) { + if FileManager.default.isReadableFile(atPath: path) { + lmSymbols.open(path) + IME.prtDebugIntel("lmSymbol: \(lmSymbols.count) entries of data loaded from: \(path)") + } else { + IME.prtDebugIntel("lmSymbols: File access failure: \(path)") + } + } + + public func loadUserPhrases(path: String, filterPath: String) { + if FileManager.default.isReadableFile(atPath: path) { + lmUserPhrases.close() + lmUserPhrases.open(path) + IME.prtDebugIntel("lmUserPhrases: \(lmUserPhrases.count) entries of data loaded from: \(path)") + } else { + IME.prtDebugIntel("lmUserPhrases: File access failure: \(path)") + } + if FileManager.default.isReadableFile(atPath: filterPath) { + lmFiltered.close() + lmFiltered.open(filterPath) + IME.prtDebugIntel("lmFiltered: \(lmFiltered.count) entries of data loaded from: \(path)") + } else { + IME.prtDebugIntel("lmFiltered: File access failure: \(path)") + } + } + + public func loadUserSymbolData(path: String) { + if FileManager.default.isReadableFile(atPath: path) { + lmUserSymbols.close() + lmUserSymbols.open(path) + IME.prtDebugIntel("lmUserSymbol: \(lmUserSymbols.count) entries of data loaded from: \(path)") + } else { + IME.prtDebugIntel("lmUserSymbol: File access failure: \(path)") + } + } + + public func loadUserAssociatedPhrases(path: String) { + if FileManager.default.isReadableFile(atPath: path) { + lmAssociates.close() + lmAssociates.open(path) + IME.prtDebugIntel("lmAssociates: \(lmAssociates.count) entries of data loaded from: \(path)") + } else { + IME.prtDebugIntel("lmAssociates: File access failure: \(path)") + } + } + + public func loadPhraseReplacementMap(path: String) { + if FileManager.default.isReadableFile(atPath: path) { + lmReplacements.close() + lmReplacements.open(path) + IME.prtDebugIntel("lmReplacements: \(lmReplacements.count) entries of data loaded from: \(path)") + } else { + IME.prtDebugIntel("lmReplacements: File access failure: \(path)") + } + } + + // MARK: - Core Functions (Public) + + /// Not implemented since we do not have data to provide bigram function. + // public func bigramsForKeys(preceedingKey: String, key: String) -> [Megrez.Bigram] { } + + /// Returns a list of available unigram for the given key. + /// @param key:String represents the BPMF reading or a symbol key. + /// For instance, it you pass "ㄉㄨㄟˇ", it returns "㨃" and other possible candidates. + override open func unigramsFor(key: String) -> [Megrez.Unigram] { + if key == " " { + /// 給空格鍵指定輸出值。 + let spaceUnigram = Megrez.Unigram( + keyValue: Megrez.KeyValuePair(key: " ", value: " "), + score: 0 + ) + return [spaceUnigram] + } + + /// 準備不同的語言模組容器,開始逐漸往容器陣列內塞入資料。 + var rawAllUnigrams: [Megrez.Unigram] = [] + + // 用 reversed 指令讓使用者語彙檔案內的詞條優先順序隨著行數增加而逐漸增高。 + // 這樣一來就可以在就地新增語彙時徹底複寫優先權。 + // 將兩句差分也是為了讓 rawUserUnigrams 的類型不受可能的影響。 + rawAllUnigrams += lmUserPhrases.unigramsFor(key: key, score: 0.0).reversed() + if lmUserPhrases.unigramsFor(key: key).isEmpty { + IME.prtDebugIntel("Not found in UserPhrasesUnigram(\(lmUserPhrases.count)): \(key)") + } + + // LMMisc 與 LMCore 的 score 在 (-10.0, 0.0) 這個區間內。 + rawAllUnigrams += lmMisc.unigramsFor(key: key) + rawAllUnigrams += lmCore.unigramsFor(key: key) + + if isCNSEnabled { + rawAllUnigrams += lmCNS.unigramsFor(key: key, score: -11) + } + + if isSymbolEnabled { + rawAllUnigrams += lmUserSymbols.unigramsFor(key: key, score: -12.0) + if lmUserSymbols.unigramsFor(key: key).isEmpty { + IME.prtDebugIntel("Not found in UserSymbolUnigram(\(lmUserSymbols.count)): \(key)") + } + + rawAllUnigrams += lmSymbols.unigramsFor(key: key) + } + + // 準備過濾清單與統計清單 + var insertedPairs: Set = [] // 統計清單 + var filteredPairs: Set = [] // 過濾清單 + + // 載入要過濾的 KeyValuePair 清單。 + for unigram in lmFiltered.unigramsFor(key: key) { + filteredPairs.insert(unigram.keyValue) + } + + var debugOutput = "\n" + for neta in rawAllUnigrams { + debugOutput += "RAW: \(neta.keyValue.key) \(neta.keyValue.value) \(neta.score)\n" + } + if debugOutput == "\n" { + debugOutput = "RAW: No match found in all unigrams." + } + IME.prtDebugIntel(debugOutput) + + return filterAndTransform( + unigrams: rawAllUnigrams, + filter: filteredPairs, inserted: &insertedPairs + ) + } + + /// If the model has unigrams for the given key. + /// @param key The key. + override open func hasUnigramsFor(key: String) -> Bool { + if key == " " { return true } + + if !lmFiltered.hasUnigramsFor(key: key) { + return lmUserPhrases.hasUnigramsFor(key: key) || lmCore.hasUnigramsFor(key: key) + } + + return !unigramsFor(key: key).isEmpty + } + + public func associatedPhrasesForKey(_ key: String) -> [String] { + lmAssociates.valuesFor(key: key) ?? [] + } + + public func hasAssociatedPhrasesForKey(_ key: String) -> Bool { + lmAssociates.hasValuesFor(key: key) + } + + // MARK: - Core Functions (Private) + + func filterAndTransform( + unigrams: [Megrez.Unigram], + filter filteredPairs: Set, + inserted insertedPairs: inout Set + ) -> [Megrez.Unigram] { + var results: [Megrez.Unigram] = [] + + for unigram in unigrams { + var pair: Megrez.KeyValuePair = unigram.keyValue + if filteredPairs.contains(pair) { + continue + } + + if isPhraseReplacementEnabled { + let replacement = lmReplacements.valuesFor(key: pair.value) + if !replacement.isEmpty { + IME.prtDebugIntel("\(pair.value) -> \(replacement)") + pair.value = replacement + } + } + + if !insertedPairs.contains(pair) { + results.append(Megrez.Unigram(keyValue: pair, score: unigram.score)) + insertedPairs.insert(pair) + } + } + return results + } + } +} diff --git a/Source/Modules/LangModelRelated/SubLanguageModels/ParselessLM.cpp b/Source/Modules/LangModelRelated/OldFileReferences/ParselessLM.cpp similarity index 100% rename from Source/Modules/LangModelRelated/SubLanguageModels/ParselessLM.cpp rename to Source/Modules/LangModelRelated/OldFileReferences/ParselessLM.cpp diff --git a/Source/Modules/LangModelRelated/SubLanguageModels/ParselessLM.h b/Source/Modules/LangModelRelated/OldFileReferences/ParselessLM.h similarity index 100% rename from Source/Modules/LangModelRelated/SubLanguageModels/ParselessLM.h rename to Source/Modules/LangModelRelated/OldFileReferences/ParselessLM.h diff --git a/Source/Modules/LangModelRelated/SubLanguageModels/ParselessPhraseDB.cpp b/Source/Modules/LangModelRelated/OldFileReferences/ParselessPhraseDB.cpp similarity index 100% rename from Source/Modules/LangModelRelated/SubLanguageModels/ParselessPhraseDB.cpp rename to Source/Modules/LangModelRelated/OldFileReferences/ParselessPhraseDB.cpp diff --git a/Source/Modules/LangModelRelated/SubLanguageModels/ParselessPhraseDB.h b/Source/Modules/LangModelRelated/OldFileReferences/ParselessPhraseDB.h similarity index 100% rename from Source/Modules/LangModelRelated/SubLanguageModels/ParselessPhraseDB.h rename to Source/Modules/LangModelRelated/OldFileReferences/ParselessPhraseDB.h diff --git a/Source/Modules/LangModelRelated/SubLanguageModels/UserOverrideModel.cpp b/Source/Modules/LangModelRelated/OldFileReferences/UserOverrideModel.cpp similarity index 98% rename from Source/Modules/LangModelRelated/SubLanguageModels/UserOverrideModel.cpp rename to Source/Modules/LangModelRelated/OldFileReferences/UserOverrideModel.cpp index 4ae8443f..8b4fb8ac 100644 --- a/Source/Modules/LangModelRelated/SubLanguageModels/UserOverrideModel.cpp +++ b/Source/Modules/LangModelRelated/OldFileReferences/UserOverrideModel.cpp @@ -34,7 +34,7 @@ namespace vChewing { // About 20 generations. -static const double DecayThreshould = 1.0 / 1048576.0; +static const double DecayThreshold = 1.0 / 1048576.0; static double Score(size_t eventCount, size_t totalCount, double eventTimestamp, double timestamp, double lambda); static bool IsEndingPunctuation(const std::string &value); @@ -126,7 +126,7 @@ void UserOverrideModel::Observation::update(const std::string &candidate, double static double Score(size_t eventCount, size_t totalCount, double eventTimestamp, double timestamp, double lambda) { double decay = exp((timestamp - eventTimestamp) * lambda); - if (decay < DecayThreshould) + if (decay < DecayThreshold) { return 0.0; } diff --git a/Source/Modules/LangModelRelated/SubLanguageModels/UserOverrideModel.h b/Source/Modules/LangModelRelated/OldFileReferences/UserOverrideModel.h similarity index 100% rename from Source/Modules/LangModelRelated/SubLanguageModels/UserOverrideModel.h rename to Source/Modules/LangModelRelated/OldFileReferences/UserOverrideModel.h diff --git a/Source/Modules/LangModelRelated/SubLMs/lmAssociates.swift b/Source/Modules/LangModelRelated/SubLMs/lmAssociates.swift new file mode 100644 index 00000000..e5aca9f7 --- /dev/null +++ b/Source/Modules/LangModelRelated/SubLMs/lmAssociates.swift @@ -0,0 +1,120 @@ +// Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// Refactored from the ObjCpp-version of this class by: +// (c) 2011 and onwards The OpenVanilla Project (MIT License). +/* +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +import Foundation + +extension vChewing { + @frozen public struct LMAssociates { + var keyValueMap: [String: [Megrez.KeyValuePair]] = [:] + + public var count: Int { + keyValueMap.count + } + + public init() { + keyValueMap = [:] + } + + public func isLoaded() -> Bool { + !keyValueMap.isEmpty + } + + @discardableResult public mutating func open(_ path: String) -> Bool { + if isLoaded() { + return false + } + + LMConsolidator.fixEOF(path: path) + LMConsolidator.consolidate(path: path, pragma: true) + + var arrData: [String] = [] + + do { + arrData = try String(contentsOfFile: path, encoding: .utf8).components(separatedBy: "\n") + } catch { + IME.prtDebugIntel("\(error)") + IME.prtDebugIntel("↑ Exception happened when reading Associated Phrases data.") + return false + } + + for (lineID, lineContent) in arrData.enumerated() { + if !lineContent.hasPrefix("#") { + let lineContent = lineContent.replacingOccurrences(of: "\t", with: " ") + if lineContent.components(separatedBy: " ").count < 2 { + if lineContent != "", lineContent != " " { + IME.prtDebugIntel("Line #\(lineID + 1) Wrecked: \(lineContent)") + } + continue + } + var currentKV = Megrez.KeyValuePair() + for (unitID, unitContent) in lineContent.components(separatedBy: " ").enumerated() { + switch unitID { + case 0: + currentKV.key = unitContent + case 1: + currentKV.value = unitContent + default: break + } + } + keyValueMap[currentKV.key, default: []].append(currentKV) + } + } + return true + } + + public mutating func close() { + if isLoaded() { + keyValueMap.removeAll() + } + } + + public func dump() { + var strDump = "" + for entry in keyValueMap { + let rows: [Megrez.KeyValuePair] = entry.value + for row in rows { + let addline = row.key + " " + row.value + "\n" + strDump += addline + } + } + IME.prtDebugIntel(strDump) + } + + public func valuesFor(key: String) -> [String]? { + var v: [String] = [] + if let matched = keyValueMap[key] { + for entry in matched as [Megrez.KeyValuePair] { + v.append(entry.value) + } + } + return v + } + + public func hasValuesFor(key: String) -> Bool { + keyValueMap[key] != nil + } + } +} diff --git a/Source/Modules/LangModelRelated/SubLMs/lmCore.swift b/Source/Modules/LangModelRelated/SubLMs/lmCore.swift new file mode 100644 index 00000000..8e63ceee --- /dev/null +++ b/Source/Modules/LangModelRelated/SubLMs/lmCore.swift @@ -0,0 +1,155 @@ +// Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License). +/* +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +// 威注音重新設計原廠詞庫語言模組。不排序,但使用 Swift 內建的 String 處理。 + +import Foundation + +extension vChewing { + @frozen public struct LMCore { + var keyValueScoreMap: [String: [Megrez.Unigram]] = [:] + var shouldReverse: Bool = false + var allowConsolidation: Bool = false + var defaultScore: Double = 0 + var shouldForceDefaultScore: Bool = false + + public var count: Int { + keyValueScoreMap.count + } + + public init( + reverse: Bool = false, consolidate: Bool = false, defaultScore scoreDefault: Double = 0, + forceDefaultScore: Bool = false + ) { + keyValueScoreMap = [:] + allowConsolidation = consolidate + shouldReverse = reverse + defaultScore = scoreDefault + shouldForceDefaultScore = forceDefaultScore + } + + public func isLoaded() -> Bool { + !keyValueScoreMap.isEmpty + } + + @discardableResult public mutating func open(_ path: String) -> Bool { + if isLoaded() { + return false + } + + if allowConsolidation { + LMConsolidator.fixEOF(path: path) + LMConsolidator.consolidate(path: path, pragma: true) + } + + var arrData: [String] = [] + + do { + arrData = try String(contentsOfFile: path, encoding: .utf8).components(separatedBy: "\n") + } catch { + IME.prtDebugIntel("\(error)") + IME.prtDebugIntel("↑ Exception happened when reading Associated Phrases data.") + return false + } + + for (lineID, lineContent) in arrData.enumerated() { + if !lineContent.hasPrefix("#") { + let lineContent = lineContent.replacingOccurrences(of: "\t", with: " ") + if lineContent.components(separatedBy: " ").count < 2 { + if lineContent != "", lineContent != " " { + IME.prtDebugIntel("Line #\(lineID + 1) Wrecked: \(lineContent)") + } + continue + } + var currentUnigram = Megrez.Unigram(keyValue: Megrez.KeyValuePair(), score: defaultScore) + var columnOne = "" + var columnTwo = "" + for (unitID, unitContent) in lineContent.components(separatedBy: " ").enumerated() { + switch unitID { + case 0: + columnOne = unitContent + case 1: + columnTwo = unitContent + case 2: + if !shouldForceDefaultScore { + if let unitContentConverted = Double(unitContent) { + currentUnigram.score = unitContentConverted + } else { + IME.prtDebugIntel("Line #\(lineID) Score Data Wrecked: \(lineContent)") + } + } + default: break + } + } + // 標點符號的頻率最好鎖定一下。 + if columnOne.contains("_punctuation_") { + currentUnigram.score -= (Double(lineID) * 0.000001) + } + let kvPair = + shouldReverse + ? Megrez.KeyValuePair(key: columnTwo, value: columnOne) + : Megrez.KeyValuePair(key: columnOne, value: columnTwo) + currentUnigram.keyValue = kvPair + let key = shouldReverse ? columnTwo : columnOne + keyValueScoreMap[key, default: []].append(currentUnigram) + } + } + return true + } + + public mutating func close() { + if isLoaded() { + keyValueScoreMap.removeAll() + } + } + + // MARK: - Advanced features + + public func dump() { + var strDump = "" + for entry in keyValueScoreMap { + let rows: [Megrez.Unigram] = entry.value + for row in rows { + let addline = row.keyValue.key + " " + row.keyValue.value + " " + String(row.score) + "\n" + strDump += addline + } + } + IME.prtDebugIntel(strDump) + } + + public func bigramsForKeys(precedingKey: String, key: String) -> [Megrez.Bigram] { + // 這裡用了點廢話處理,不然函數構建體會被 Swift 格式整理工具給毀掉。 + // 其實只要一句「[Megrez.Bigram]()」就夠了。 + precedingKey == key ? [Megrez.Bigram]() : [Megrez.Bigram]() + } + + public func unigramsFor(key: String) -> [Megrez.Unigram] { + keyValueScoreMap[key] ?? [Megrez.Unigram]() + } + + public func hasUnigramsFor(key: String) -> Bool { + keyValueScoreMap[key] != nil + } + } +} diff --git a/Source/Modules/LangModelRelated/SubLMs/lmLite.swift b/Source/Modules/LangModelRelated/SubLMs/lmLite.swift new file mode 100644 index 00000000..62e3ebac --- /dev/null +++ b/Source/Modules/LangModelRelated/SubLMs/lmLite.swift @@ -0,0 +1,124 @@ +// Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// Refactored from the ObjCpp-version of this class by: +// (c) 2011 and onwards The OpenVanilla Project (MIT License). +/* +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +import Foundation + +extension vChewing { + @frozen public struct LMLite { + var keyValueMap: [String: [Megrez.KeyValuePair]] = [:] + var allowConsolidation = false + + public var count: Int { + keyValueMap.count + } + + public init(consolidate: Bool = false) { + keyValueMap = [:] + allowConsolidation = consolidate + } + + public func isLoaded() -> Bool { + !keyValueMap.isEmpty + } + + @discardableResult public mutating func open(_ path: String) -> Bool { + if isLoaded() { + return false + } + + if allowConsolidation { + LMConsolidator.fixEOF(path: path) + LMConsolidator.consolidate(path: path, pragma: true) + } + + var arrData: [String] = [] + + do { + arrData = try String(contentsOfFile: path, encoding: .utf8).components(separatedBy: "\n") + } catch { + IME.prtDebugIntel("\(error)") + IME.prtDebugIntel("↑ Exception happened when reading Associated Phrases data.") + return false + } + + for (lineID, lineContent) in arrData.enumerated() { + if !lineContent.hasPrefix("#") { + let lineContent = lineContent.replacingOccurrences(of: "\t", with: " ") + if lineContent.components(separatedBy: " ").count < 2 { + if lineContent != "", lineContent != " " { + IME.prtDebugIntel("Line #\(lineID + 1) Wrecked: \(lineContent)") + } + continue + } + var currentKV = Megrez.KeyValuePair() + for (unitID, unitContent) in lineContent.components(separatedBy: " ").enumerated() { + switch unitID { + case 0: + currentKV.value = unitContent + case 1: + currentKV.key = unitContent + default: break + } + } + keyValueMap[currentKV.key, default: []].append(currentKV) + } + } + return true + } + + public mutating func close() { + if isLoaded() { + keyValueMap.removeAll() + } + } + + public func dump() { + var strDump = "" + for entry in keyValueMap { + let rows: [Megrez.KeyValuePair] = entry.value + for row in rows { + let addline = row.key + " " + row.value + "\n" + strDump += addline + } + } + IME.prtDebugIntel(strDump) + } + + public func unigramsFor(key: String, score givenScore: Double = 0.0) -> [Megrez.Unigram] { + var v: [Megrez.Unigram] = [] + if let matched = keyValueMap[key] { + for entry in matched as [Megrez.KeyValuePair] { + v.append(Megrez.Unigram(keyValue: entry, score: givenScore)) + } + } + return v + } + + public func hasUnigramsFor(key: String) -> Bool { + keyValueMap[key] != nil + } + } +} diff --git a/Source/Modules/LangModelRelated/SubLMs/lmReplacements.swift b/Source/Modules/LangModelRelated/SubLMs/lmReplacements.swift new file mode 100644 index 00000000..9cfc5ff5 --- /dev/null +++ b/Source/Modules/LangModelRelated/SubLMs/lmReplacements.swift @@ -0,0 +1,107 @@ +// Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// Refactored from the ObjCpp-version of this class by: +// (c) 2011 and onwards The OpenVanilla Project (MIT License). +/* +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +import Foundation + +extension vChewing { + @frozen public struct LMReplacments { + var keyValueMap: [String: String] = [:] + + public var count: Int { + keyValueMap.count + } + + public init() { + keyValueMap = [:] + } + + public func isLoaded() -> Bool { + !keyValueMap.isEmpty + } + + @discardableResult public mutating func open(_ path: String) -> Bool { + if isLoaded() { + return false + } + + LMConsolidator.fixEOF(path: path) + LMConsolidator.consolidate(path: path, pragma: true) + + var arrData: [String] = [] + + do { + arrData = try String(contentsOfFile: path, encoding: .utf8).components(separatedBy: "\n") + + } catch { + IME.prtDebugIntel("\(error)") + IME.prtDebugIntel("↑ Exception happened when reading Associated Phrases data.") + return false + } + + for (lineID, lineContent) in arrData.enumerated() { + if !lineContent.hasPrefix("#") { + let lineContent = lineContent.replacingOccurrences(of: "\t", with: " ") + if lineContent.components(separatedBy: " ").count < 2 { + if lineContent != "", lineContent != " " { + IME.prtDebugIntel("Line #\(lineID + 1) Wrecked: \(lineContent)") + } + continue + } + var currentKV = Megrez.KeyValuePair() + for (unitID, unitContent) in lineContent.components(separatedBy: " ").enumerated() { + switch unitID { + case 0: + currentKV.key = unitContent + case 1: + currentKV.value = unitContent + default: break + } + } + keyValueMap[currentKV.key] = currentKV.value + } + } + return true + } + + public mutating func close() { + if isLoaded() { + keyValueMap.removeAll() + } + } + + public func dump() { + var strDump = "" + for entry in keyValueMap { + strDump += entry.key + " " + entry.value + "\n" + } + IME.prtDebugIntel(strDump) + } + + public func valuesFor(key: String) -> String { + keyValueMap[key] ?? "" + } + } +} diff --git a/Source/Modules/LangModelRelated/SubLMs/lmUserOverride.swift b/Source/Modules/LangModelRelated/SubLMs/lmUserOverride.swift new file mode 100644 index 00000000..24f74120 --- /dev/null +++ b/Source/Modules/LangModelRelated/SubLMs/lmUserOverride.swift @@ -0,0 +1,223 @@ +// Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// Refactored from the ObjCpp-version of this class by: +// (c) 2011 and onwards The OpenVanilla Project (MIT License). +/* +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +import Foundation + +extension vChewing { + public class LMUserOverride { + // MARK: - Private Structures + + struct Override { + var count: Int = 0 + var timestamp: Double = 0.0 + } + + struct Observation { + var count: Int = 0 + var overrides: [String: Override] = [:] + + mutating func update(candidate: String, timestamp: Double) { + count += 1 + if var neta = overrides[candidate] { + neta.timestamp = timestamp + neta.count += 1 + } + } + } + + struct KeyObservationPair: Equatable { + var key: String + var observation: Observation + + var hashValue: Int { key.hashValue } + + init(key: String, observation: Observation) { + self.key = key + self.observation = observation + } + + static func == (lhs: KeyObservationPair, rhs: KeyObservationPair) -> Bool { + lhs.key == rhs.key + } + } + + // MARK: - Main + + var mutCapacity: Int + var mutDecayExponent: Double + var mutLRUList = [KeyObservationPair]() + var mutLRUMap: [String: KeyObservationPair] = [:] + let kDecayThreshold: Double = 1.0 / 1_048_576.0 + + public init(capacity: Int = 500, decayConstant: Double = 5400.0) { + mutCapacity = abs(capacity) // Ensures that this value is always > 0. + mutDecayExponent = log(0.5) / decayConstant + } + + public func observe( + walkedNodes: [Megrez.NodeAnchor], + cursorIndex: Int, + candidate: String, + timestamp: Double + ) { + let key = getWalkedNodesToKey(walkedNodes: walkedNodes, cursorIndex: cursorIndex) + guard !key.isEmpty + else { + return + } + guard let map = mutLRUMap[key] else { + var observation: Observation = .init() + observation.update(candidate: candidate, timestamp: timestamp) + mutLRUMap[key] = KeyObservationPair(key: key, observation: observation) + mutLRUList.insert(KeyObservationPair(key: key, observation: observation), at: 0) + + if mutLRUList.count > mutCapacity { + mutLRUMap[mutLRUList.reversed()[0].key] = nil + mutLRUList.removeLast() + } + return + } + var obs = map.observation + obs.update(candidate: candidate, timestamp: timestamp) + let pair = KeyObservationPair(key: key, observation: obs) + mutLRUList.insert(pair, at: 0) + } + + public func suggest( + walkedNodes: [Megrez.NodeAnchor], + cursorIndex: Int, + timestamp: Double + ) -> String { + let key = getWalkedNodesToKey(walkedNodes: walkedNodes, cursorIndex: cursorIndex) + guard let keyValuePair = mutLRUMap[key], + !key.isEmpty + else { + return "" + } + + IME.prtDebugIntel("Suggest - A: \(key)") + IME.prtDebugIntel("Suggest - B: \(keyValuePair.key)") + + let observation = keyValuePair.observation + + var candidate = "" + var score = 0.0 + for overrideNeta in Array(observation.overrides) { + let overrideScore = getScore( + eventCount: overrideNeta.value.count, + totalCount: observation.count, + eventTimestamp: overrideNeta.value.timestamp, + timestamp: timestamp, + lambda: mutDecayExponent + ) + + if overrideScore == 0.0 { + continue + } + + if overrideScore > score { + candidate = overrideNeta.key + score = overrideScore + } + } + return candidate + } + + func isEndingPunctuation(value: String) -> Bool { + [",", "。", "!", "?", "」", "』", "”", "’"].contains(value) + } + + public func getScore( + eventCount: Int, + totalCount: Int, + eventTimestamp: Double, + timestamp: Double, + lambda: Double + ) -> Double { + let decay = exp((timestamp - eventTimestamp) * lambda) + if decay < kDecayThreshold { + return 0.0 + } + + let prob = Double(eventCount) / Double(totalCount) + return prob * decay + } + + func getWalkedNodesToKey( + walkedNodes: [Megrez.NodeAnchor], cursorIndex: Int + ) -> String { + var strOutput = "" + var arrNodes: [Megrez.NodeAnchor] = [] + var intLength = 0 + for nodeNeta in walkedNodes { + arrNodes.append(nodeNeta) + intLength += nodeNeta.spanningLength + if intLength >= cursorIndex { + break + } + } + + // 一個被 .reversed 過的陣列不能直接使用,因為不是正常的 Swift 陣列。 + // 那就新開一個正常的陣列、然後將內容拓印過去。 + var arrNodesReversed: [Megrez.NodeAnchor] = [] + arrNodesReversed.append(contentsOf: arrNodes.reversed()) + + if arrNodesReversed.isEmpty { + return "" + } + + var strCurrent = "()" + var strPrev = "()" + var strAnterior = "()" + + for (theIndex, theAnchor) in arrNodesReversed.enumerated() { + if strCurrent != "()", let nodeCurrent = theAnchor.node { + let keyCurrent = nodeCurrent.currentKeyValue().key + let valCurrent = nodeCurrent.currentKeyValue().value + strCurrent = "(\(keyCurrent), \(valCurrent))" + if let nodePrev = arrNodesReversed[theIndex + 1].node { + let keyPrev = nodePrev.currentKeyValue().key + let valPrev = nodePrev.currentKeyValue().value + strPrev = "(\(keyPrev), \(valPrev))" + } + if let nodeAnterior = arrNodesReversed[theIndex + 2].node { + let keyAnterior = nodeAnterior.currentKeyValue().key + let valAnterior = nodeAnterior.currentKeyValue().value + strAnterior = "(\(keyAnterior), \(valAnterior))" + } + break // 我們只取第一個有效結果。 + } + } + + strOutput = "(\(strAnterior),\(strPrev),\(strCurrent))" + if strOutput == "((),(),())" { + strOutput = "" + } + + return strOutput + } + } +} diff --git a/Source/Modules/LangModelRelated/SubLanguageModels/AssociatedPhrases.h b/Source/Modules/LangModelRelated/SubLanguageModels/AssociatedPhrases.h deleted file mode 100644 index 63f6aca1..00000000 --- a/Source/Modules/LangModelRelated/SubLanguageModels/AssociatedPhrases.h +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are of: -// (c) 2021 and onwards The vChewing Project (MIT-NTL License). -/* -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -1. The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -2. No trademark license is granted to use the trade names, trademarks, service -marks, or product names of Contributor, except as required to fulfill notice -requirements above. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -#ifndef ASSOCIATEDPHRASES_H -#define ASSOCIATEDPHRASES_H - -#include -#include -#include -#include - -namespace vChewing -{ - -class AssociatedPhrases -{ - public: - AssociatedPhrases(); - ~AssociatedPhrases(); - - const bool isLoaded(); - bool open(const char *path); - void close(); - const std::vector valuesForKey(const std::string &key); - const bool hasValuesForKey(const std::string &key); - - protected: - struct Row - { - Row(std::string_view &k, std::string_view &v) : key(k), value(v) - { - } - std::string_view key; - std::string_view value; - }; - - std::map> keyRowMap; - - int fd; - void *data; - size_t length; -}; - -} // namespace vChewing - -#endif /* AssociatedPhrases_hpp */ diff --git a/Source/Modules/LangModelRelated/SubLanguageModels/AssociatedPhrases.mm b/Source/Modules/LangModelRelated/SubLanguageModels/AssociatedPhrases.mm deleted file mode 100644 index ac0f223e..00000000 --- a/Source/Modules/LangModelRelated/SubLanguageModels/AssociatedPhrases.mm +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are of: -// (c) 2021 and onwards The vChewing Project (MIT-NTL License). -/* -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -1. The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -2. No trademark license is granted to use the trade names, trademarks, service -marks, or product names of Contributor, except as required to fulfill notice -requirements above. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -#include "AssociatedPhrases.h" -#include "vChewing-Swift.h" -#include -#include -#include -#include -#include - -#include "KeyValueBlobReader.h" -#include "LMConsolidator.h" - -namespace vChewing -{ - -AssociatedPhrases::AssociatedPhrases() : fd(-1), data(0), length(0) -{ -} - -AssociatedPhrases::~AssociatedPhrases() -{ - if (data) - { - close(); - } -} - -const bool AssociatedPhrases::isLoaded() -{ - if (data) - { - return true; - } - return false; -} - -bool AssociatedPhrases::open(const char *path) -{ - if (data) - { - return false; - } - - LMConsolidator::FixEOF(path); - LMConsolidator::ConsolidateContent(path, true); - - fd = ::open(path, O_RDONLY); - if (fd == -1) - { - printf("open:: file not exist"); - return false; - } - - struct stat sb; - if (fstat(fd, &sb) == -1) - { - printf("open:: cannot open file"); - return false; - } - - length = (size_t)sb.st_size; - - data = mmap(NULL, length, PROT_READ, MAP_SHARED, fd, 0); - if (!data) - { - ::close(fd); - return false; - } - - KeyValueBlobReader reader(static_cast(data), length); - KeyValueBlobReader::KeyValue keyValue; - KeyValueBlobReader::State state; - while ((state = reader.Next(&keyValue)) == KeyValueBlobReader::State::HAS_PAIR) - { - keyRowMap[keyValue.key].emplace_back(keyValue.key, keyValue.value); - } - // 下面這一段或許可以做成開關、來詢問是否對使用者語彙採取寬鬆策略(哪怕有行內容寫錯也會放行) - if (state == KeyValueBlobReader::State::ERROR) - { - // close(); - if (mgrPrefs.isDebugModeEnabled) - syslog(LOG_CONS, "AssociatedPhrases: Failed at Open Step 5. On Error Resume Next.\n"); - // return false; - } - return true; -} - -void AssociatedPhrases::close() -{ - if (data) - { - munmap(data, length); - ::close(fd); - data = 0; - } - - keyRowMap.clear(); -} - -const std::vector AssociatedPhrases::valuesForKey(const std::string &key) -{ - std::vector v; - auto iter = keyRowMap.find(key); - if (iter != keyRowMap.end()) - { - const std::vector &rows = iter->second; - for (const auto &row : rows) - { - std::string_view value = row.value; - v.push_back({value.data(), value.size()}); - } - } - return v; -} - -const bool AssociatedPhrases::hasValuesForKey(const std::string &key) -{ - return keyRowMap.find(key) != keyRowMap.end(); -} - -}; // namespace vChewing diff --git a/Source/Modules/LangModelRelated/SubLanguageModels/CoreLM.h b/Source/Modules/LangModelRelated/SubLanguageModels/CoreLM.h deleted file mode 100644 index 46625e74..00000000 --- a/Source/Modules/LangModelRelated/SubLanguageModels/CoreLM.h +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are of: -// (c) 2021 and onwards The vChewing Project (MIT-NTL License). -/* -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -1. The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -2. No trademark license is granted to use the trade names, trademarks, service -marks, or product names of Contributor, except as required to fulfill notice -requirements above. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -#ifndef CoreLM_H -#define CoreLM_H - -#include "LanguageModel.h" -#include -#include -#include -#include - -// this class relies on the fact that we have a space-separated data -// format, and we use mmap and zero-out the separators and line feeds -// to avoid creating new string objects; the parser is a simple DFA - -using namespace std; -using namespace Gramambular; - -namespace vChewing -{ - -class CoreLM : public Gramambular::LanguageModel -{ - public: - CoreLM(); - ~CoreLM(); - - bool isLoaded(); - bool open(const char *path); - void close(); - void dump(); - - virtual const std::vector bigramsForKeys(const string &preceedingKey, const string &key); - virtual const std::vector unigramsForKey(const string &key); - virtual bool hasUnigramsForKey(const string &key); - - protected: - struct CStringCmp - { - bool operator()(const char *s1, const char *s2) const - { - return strcmp(s1, s2) < 0; - } - }; - - struct Row - { - const char *key; - const char *value; - const char *logProbability; - }; - - map, CStringCmp> keyRowMap; - int fd; - void *data; - size_t length; -}; - -}; // namespace vChewing - -#endif diff --git a/Source/Modules/LangModelRelated/SubLanguageModels/CoreLM.mm b/Source/Modules/LangModelRelated/SubLanguageModels/CoreLM.mm deleted file mode 100644 index de24f821..00000000 --- a/Source/Modules/LangModelRelated/SubLanguageModels/CoreLM.mm +++ /dev/null @@ -1,365 +0,0 @@ -// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are of: -// (c) 2021 and onwards The vChewing Project (MIT-NTL License). -/* -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -1. The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -2. No trademark license is granted to use the trade names, trademarks, service -marks, or product names of Contributor, except as required to fulfill notice -requirements above. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -#include "CoreLM.h" -#include "vChewing-Swift.h" -#include -#include -#include -#include -#include -#include - -using namespace Gramambular; - -vChewing::CoreLM::CoreLM() : fd(-1), data(0), length(0) -{ -} - -vChewing::CoreLM::~CoreLM() -{ - if (data) - { - close(); - } -} - -bool vChewing::CoreLM::isLoaded() -{ - if (data) - { - return true; - } - return false; -} - -bool vChewing::CoreLM::open(const char *path) -{ - if (data) - { - return false; - } - - fd = ::open(path, O_RDONLY); - if (fd == -1) - { - return false; - } - - struct stat sb; - if (fstat(fd, &sb) == -1) - { - return false; - } - - length = (size_t)sb.st_size; - - data = mmap(NULL, length, PROT_WRITE, MAP_PRIVATE, fd, 0); - if (!data) - { - ::close(fd); - return false; - } - - // Regular expression for parsing: - // (\n*\w\w*\s\w\w*\s\w\w*)*$ - // - // Expanded as DFA (in Graphviz): - // - // digraph finite_state_machine { - // rankdir = LR; - // size = "10"; - // - // node [shape = doublecircle]; End; - // node [shape = circle]; - // - // Start -> End [ label = "EOF"]; - // Start -> Error [ label = "\\s" ]; - // Start -> Start [ label = "\\n" ]; - // Start -> 1 [ label = "\\w" ]; - // - // 1 -> Error [ label = "\\n, EOF" ]; - // 1 -> 2 [ label = "\\s" ]; - // 1 -> 1 [ label = "\\w" ]; - // - // 2 -> Error [ label = "\\n, \\s, EOF" ]; - // 2 -> 3 [ label = "\\w" ]; - // - // 3 -> Error [ label = "\\n, EOF "]; - // 3 -> 4 [ label = "\\s" ]; - // 3 -> 3 [ label = "\\w" ]; - // - // 4 -> Error [ label = "\\n, \\s, EOF" ]; - // 4 -> 5 [ label = "\\w" ]; - // - // 5 -> Error [ label = "\\s, EOF" ]; - // 5 -> Start [ label = "\\n" ]; - // 5 -> 5 [ label = "\\w" ]; - // } - - char *head = (char *)data; - char *end = (char *)data + length; - char c; - Row row; - -start: - // EOF -> end - if (head == end) - { - goto end; - } - - c = *head; - // \s -> error - if (c == ' ') - { - if (mgrPrefs.isDebugModeEnabled) - syslog(LOG_CONS, "vChewingDebug: CoreLM // Start: \\s -> error"); - goto error; - } - // \n -> start - else if (c == '\n') - { - head++; - goto start; - } - - // \w -> record column star, state1 - row.value = head; - head++; - // fall through to state 1 - -state1: - // EOF -> error - if (head == end) - { - if (mgrPrefs.isDebugModeEnabled) - syslog(LOG_CONS, "vChewingDebug: CoreLM // state 1: EOF -> error"); - goto error; - } - - c = *head; - // \n -> error - if (c == '\n') - { - if (mgrPrefs.isDebugModeEnabled) - syslog(LOG_CONS, "vChewingDebug: CoreLM // state 1: \\n -> error"); - goto error; - } - // \s -> state2 + zero out ending + record column start - else if (c == ' ') - { - *head = 0; - head++; - row.key = head; - goto state2; - } - - // \w -> state1 - head++; - goto state1; - -state2: - // eof -> error - if (head == end) - { - if (mgrPrefs.isDebugModeEnabled) - syslog(LOG_CONS, "vChewingDebug: CoreLM // state 2: EOF -> error"); - goto error; - } - - c = *head; - // \n, \s -> error - if (c == '\n' || c == ' ') - { - if (mgrPrefs.isDebugModeEnabled) - syslog(LOG_CONS, "vChewingDebug: CoreLM // state 2: \\n \\s -> error"); - goto error; - } - - // \w -> state3 - head++; - - // fall through to state 3 - -state3: - // eof -> error - if (head == end) - { - if (mgrPrefs.isDebugModeEnabled) - syslog(LOG_CONS, "vChewingDebug: CoreLM // state 3: EOF -> error"); - goto error; - } - - c = *head; - - // \n -> error - if (c == '\n') - { - if (mgrPrefs.isDebugModeEnabled) - syslog(LOG_CONS, "vChewingDebug: CoreLM // state 3: \\n -> error"); - goto error; - } - // \s -> state4 + zero out ending + record column start - else if (c == ' ') - { - *head = 0; - head++; - row.logProbability = head; - goto state4; - } - - // \w -> state3 - head++; - goto state3; - -state4: - // eof -> error - if (head == end) - { - if (mgrPrefs.isDebugModeEnabled) - syslog(LOG_CONS, "vChewingDebug: CoreLM // state 4: EOF -> error"); - goto error; - } - - c = *head; - // \n, \s -> error - if (c == '\n' || c == ' ') - { - if (mgrPrefs.isDebugModeEnabled) - syslog(LOG_CONS, "vChewingDebug: CoreLM // state 4: \\n \\s -> error"); - goto error; - } - - // \w -> state5 - head++; - - // fall through to state 5 - -state5: - // eof -> error - if (head == end) - { - if (mgrPrefs.isDebugModeEnabled) - syslog(LOG_CONS, "vChewingDebug: CoreLM // state 5: EOF -> error"); - goto error; - } - - c = *head; - // \s -> error - if (c == ' ') - { - if (mgrPrefs.isDebugModeEnabled) - syslog(LOG_CONS, "vChewingDebug: CoreLM // state 5: \\s -> error"); - goto error; - } - // \n -> start - else if (c == '\n') - { - *head = 0; - head++; - keyRowMap[row.key].push_back(row); - goto start; - } - - // \w -> state 5 - head++; - goto state5; - -error: - close(); - return false; - -end: - static const char *space = " "; - static const char *zero = "0.0"; - Row emptyRow; - emptyRow.key = space; - emptyRow.value = space; - emptyRow.logProbability = zero; - keyRowMap[space].push_back(emptyRow); - if (mgrPrefs.isDebugModeEnabled) - syslog(LOG_CONS, "vChewingDebug: CoreLM // File Load Complete."); - return true; -} - -void vChewing::CoreLM::close() -{ - if (data) - { - munmap(data, length); - ::close(fd); - data = 0; - } - - keyRowMap.clear(); -} - -void vChewing::CoreLM::dump() -{ - size_t rows = 0; - for (map>::const_iterator i = keyRowMap.begin(), e = keyRowMap.end(); i != e; ++i) - { - const vector &r = (*i).second; - for (vector::const_iterator ri = r.begin(), re = r.end(); ri != re; ++ri) - { - const Row &row = *ri; - cerr << row.key << " " << row.value << " " << row.logProbability << "\n"; - rows++; - } - } -} - -const std::vector vChewing::CoreLM::bigramsForKeys(const string &preceedingKey, const string &key) -{ - return std::vector(); -} - -const std::vector vChewing::CoreLM::unigramsForKey(const string &key) -{ - std::vector v; - map>::const_iterator i = keyRowMap.find(key.c_str()); - - if (i != keyRowMap.end()) - { - for (vector::const_iterator ri = (*i).second.begin(), re = (*i).second.end(); ri != re; ++ri) - { - Unigram g; - const Row &r = *ri; - g.keyValue.key = r.key; - g.keyValue.value = r.value; - g.score = atof(r.logProbability); - v.push_back(g); - } - } - - return v; -} - -bool vChewing::CoreLM::hasUnigramsForKey(const string &key) -{ - return keyRowMap.find(key.c_str()) != keyRowMap.end(); -} diff --git a/Source/Modules/LangModelRelated/SubLanguageModels/PhraseReplacementMap.h b/Source/Modules/LangModelRelated/SubLanguageModels/PhraseReplacementMap.h deleted file mode 100644 index 43263923..00000000 --- a/Source/Modules/LangModelRelated/SubLanguageModels/PhraseReplacementMap.h +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are of: -// (c) 2021 and onwards The vChewing Project (MIT-NTL License). -/* -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -1. The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -2. No trademark license is granted to use the trade names, trademarks, service -marks, or product names of Contributor, except as required to fulfill notice -requirements above. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -#ifndef PHRASEREPLACEMENTMAP_H -#define PHRASEREPLACEMENTMAP_H - -#include -#include -#include - -namespace vChewing -{ - -class PhraseReplacementMap -{ - public: - PhraseReplacementMap(); - ~PhraseReplacementMap(); - - bool open(const char *path); - void close(); - const std::string valueForKey(const std::string &key); - - protected: - std::map keyValueMap; - int fd; - void *data; - size_t length; -}; - -} // namespace vChewing - -#endif diff --git a/Source/Modules/LangModelRelated/SubLanguageModels/PhraseReplacementMap.mm b/Source/Modules/LangModelRelated/SubLanguageModels/PhraseReplacementMap.mm deleted file mode 100644 index 7fde339b..00000000 --- a/Source/Modules/LangModelRelated/SubLanguageModels/PhraseReplacementMap.mm +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are of: -// (c) 2021 and onwards The vChewing Project (MIT-NTL License). -/* -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -1. The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -2. No trademark license is granted to use the trade names, trademarks, service -marks, or product names of Contributor, except as required to fulfill notice -requirements above. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -#include "PhraseReplacementMap.h" -#include "vChewing-Swift.h" -#include -#include -#include -#include -#include -#include - -#include "KeyValueBlobReader.h" -#include "LMConsolidator.h" - -namespace vChewing -{ - -using std::string; - -PhraseReplacementMap::PhraseReplacementMap() : fd(-1), data(0), length(0) -{ -} - -PhraseReplacementMap::~PhraseReplacementMap() -{ - if (data) - { - close(); - } -} - -bool PhraseReplacementMap::open(const char *path) -{ - if (data) - { - return false; - } - - LMConsolidator::FixEOF(path); - LMConsolidator::ConsolidateContent(path, true); - - fd = ::open(path, O_RDONLY); - if (fd == -1) - { - printf("open:: file not exist"); - return false; - } - - struct stat sb; - if (fstat(fd, &sb) == -1) - { - printf("open:: cannot open file"); - return false; - } - - length = (size_t)sb.st_size; - - data = mmap(NULL, length, PROT_READ, MAP_SHARED, fd, 0); - if (!data) - { - ::close(fd); - return false; - } - - KeyValueBlobReader reader(static_cast(data), length); - KeyValueBlobReader::KeyValue keyValue; - KeyValueBlobReader::State state; - while ((state = reader.Next(&keyValue)) == KeyValueBlobReader::State::HAS_PAIR) - { - keyValueMap[keyValue.key] = keyValue.value; - } - // 下面這一段或許可以做成開關、來詢問是否對使用者語彙採取寬鬆策略(哪怕有行內容寫錯也會放行) - if (state == KeyValueBlobReader::State::ERROR) - { - // close(); - if (mgrPrefs.isDebugModeEnabled) - syslog(LOG_CONS, "PhraseReplacementMap: Failed at Open Step 5. On Error Resume Next.\n"); - // return false; - } - return true; -} - -void PhraseReplacementMap::close() -{ - if (data) - { - munmap(data, length); - ::close(fd); - data = 0; - } - - keyValueMap.clear(); -} - -const std::string PhraseReplacementMap::valueForKey(const std::string &key) -{ - auto iter = keyValueMap.find(key); - if (iter != keyValueMap.end()) - { - const std::string_view v = iter->second; - return {v.data(), v.size()}; - } - return string(""); -} - -} diff --git a/Source/Modules/LangModelRelated/SubLanguageModels/UserPhrasesLM.h b/Source/Modules/LangModelRelated/SubLanguageModels/UserPhrasesLM.h deleted file mode 100644 index 4c27d748..00000000 --- a/Source/Modules/LangModelRelated/SubLanguageModels/UserPhrasesLM.h +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are of: -// (c) 2021 and onwards The vChewing Project (MIT-NTL License). -/* -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -1. The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -2. No trademark license is granted to use the trade names, trademarks, service -marks, or product names of Contributor, except as required to fulfill notice -requirements above. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -#ifndef USERPHRASESLM_H -#define USERPHRASESLM_H - -#include "LanguageModel.h" -#include -#include -#include - -namespace vChewing -{ - -class UserPhrasesLM : public Gramambular::LanguageModel -{ - public: - UserPhrasesLM(); - ~UserPhrasesLM(); - - bool isLoaded(); - bool open(const char *path); - void close(); - void dump(); - - virtual bool allowConsolidation() - { - return true; - } - - virtual float overridedValue() - { - return 0.0; - } - - virtual const std::vector bigramsForKeys(const std::string &preceedingKey, - const std::string &key); - virtual const std::vector unigramsForKey(const std::string &key); - virtual bool hasUnigramsForKey(const std::string &key); - - protected: - struct Row - { - Row(std::string_view &k, std::string_view &v) : key(k), value(v) - { - } - std::string_view key; - std::string_view value; - }; - - std::map> keyRowMap; - int fd; - void *data; - size_t length; -}; - -} // namespace vChewing - -#endif diff --git a/Source/Modules/LangModelRelated/SubLanguageModels/UserPhrasesLM.mm b/Source/Modules/LangModelRelated/SubLanguageModels/UserPhrasesLM.mm deleted file mode 100644 index e3565d0e..00000000 --- a/Source/Modules/LangModelRelated/SubLanguageModels/UserPhrasesLM.mm +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are of: -// (c) 2021 and onwards The vChewing Project (MIT-NTL License). -/* -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -1. The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -2. No trademark license is granted to use the trade names, trademarks, service -marks, or product names of Contributor, except as required to fulfill notice -requirements above. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -#include "UserPhrasesLM.h" -#include "vChewing-Swift.h" -#include -#include -#include -#include -#include -#include - -#include "KeyValueBlobReader.h" -#include "LMConsolidator.h" - -namespace vChewing -{ - -UserPhrasesLM::UserPhrasesLM() : fd(-1), data(0), length(0) -{ -} - -UserPhrasesLM::~UserPhrasesLM() -{ - if (data) - { - close(); - } -} - -bool UserPhrasesLM::isLoaded() -{ - if (data) - { - return true; - } - return false; -} - -bool UserPhrasesLM::open(const char *path) -{ - if (data) - { - return false; - } - - if (allowConsolidation()) - { - LMConsolidator::FixEOF(path); - LMConsolidator::ConsolidateContent(path, true); - } - - fd = ::open(path, O_RDONLY); - if (fd == -1) - { - printf("open:: file not exist"); - return false; - } - - struct stat sb; - if (fstat(fd, &sb) == -1) - { - printf("open:: cannot open file"); - return false; - } - - length = (size_t)sb.st_size; - - data = mmap(NULL, length, PROT_READ, MAP_SHARED, fd, 0); - if (!data) - { - ::close(fd); - return false; - } - - KeyValueBlobReader reader(static_cast(data), length); - KeyValueBlobReader::KeyValue keyValue; - KeyValueBlobReader::State state; - while ((state = reader.Next(&keyValue)) == KeyValueBlobReader::State::HAS_PAIR) - { - // We invert the key and value, since in user phrases, "key" is the phrase value, and "value" is the BPMF - // reading. - keyRowMap[keyValue.value].emplace_back(keyValue.value, keyValue.key); - } - // 下面這一段或許可以做成開關、來詢問是否對使用者語彙採取寬鬆策略(哪怕有行內容寫錯也會放行) - if (state == KeyValueBlobReader::State::ERROR) - { - // close(); - if (mgrPrefs.isDebugModeEnabled) - syslog(LOG_CONS, "UserPhrasesLM: Failed at Open Step 5. On Error Resume Next.\n"); - // return false; - } - return true; -} - -void UserPhrasesLM::close() -{ - if (data) - { - munmap(data, length); - ::close(fd); - data = 0; - } - - keyRowMap.clear(); -} - -void UserPhrasesLM::dump() -{ - for (const auto &entry : keyRowMap) - { - const std::vector &rows = entry.second; - for (const auto &row : rows) - { - std::cerr << row.key << " " << row.value << "\n"; - } - } -} - -const std::vector UserPhrasesLM::bigramsForKeys(const std::string &preceedingKey, - const std::string &key) -{ - return std::vector(); -} - -const std::vector UserPhrasesLM::unigramsForKey(const std::string &key) -{ - std::vector v; - auto iter = keyRowMap.find(key); - if (iter != keyRowMap.end()) - { - const std::vector &rows = iter->second; - for (const auto &row : rows) - { - Gramambular::Unigram g; - g.keyValue.key = row.key; - g.keyValue.value = row.value; - g.score = overridedValue(); - v.push_back(g); - } - } - - return v; -} - -bool UserPhrasesLM::hasUnigramsForKey(const std::string &key) -{ - return keyRowMap.find(key) != keyRowMap.end(); -} - -}; // namespace vChewing diff --git a/Source/Modules/LangModelRelated/mgrLangModel.h b/Source/Modules/LangModelRelated/mgrLangModel.h deleted file mode 100644 index b7bfbae1..00000000 --- a/Source/Modules/LangModelRelated/mgrLangModel.h +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are of: -// (c) 2021 and onwards The vChewing Project (MIT-NTL License). -/* -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -1. The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -2. No trademark license is granted to use the trade names, trademarks, service -marks, or product names of Contributor, except as required to fulfill notice -requirements above. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -#import "KeyHandler.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface mgrLangModel : NSObject - -+ (void)loadDataModel:(InputMode)mode; -+ (void)loadUserPhrases; -+ (void)loadUserAssociatedPhrases; -+ (void)loadUserPhraseReplacement; - -+ (BOOL)checkIfUserPhraseExist:(NSString *)userPhrase - inputMode:(InputMode)mode - key:(NSString *)key NS_SWIFT_NAME(checkIfUserPhraseExist(userPhrase:mode:key:)); -+ (void)consolidateGivenFile:(NSString *)path shouldCheckPragma:(BOOL)shouldCheckPragma; -+ (void)setPhraseReplacementEnabled:(BOOL)phraseReplacementEnabled; -+ (void)setCNSEnabled:(BOOL)cnsEnabled; -+ (void)setSymbolEnabled:(BOOL)symbolEnabled; - -@end - -/// The following methods are merely for testing. -@interface mgrLangModel () -+ (void)loadDataModels; -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/Modules/LangModelRelated/mgrLangModel.mm b/Source/Modules/LangModelRelated/mgrLangModel.mm deleted file mode 100644 index 065a5afb..00000000 --- a/Source/Modules/LangModelRelated/mgrLangModel.mm +++ /dev/null @@ -1,195 +0,0 @@ -// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are of: -// (c) 2021 and onwards The vChewing Project (MIT-NTL License). -/* -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -1. The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -2. No trademark license is granted to use the trade names, trademarks, service -marks, or product names of Contributor, except as required to fulfill notice -requirements above. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -#import "mgrLangModel.h" -#import "LMConsolidator.h" -#import "mgrLangModel_Privates.h" -#import "vChewing-Swift.h" - -static const int kUserOverrideModelCapacity = 500; -static const double kObservedOverrideHalflife = 5400.0; - -static vChewing::LMInstantiator gLangModelCHT; -static vChewing::LMInstantiator gLangModelCHS; -static vChewing::UserOverrideModel gUserOverrideModelCHT(kUserOverrideModelCapacity, kObservedOverrideHalflife); -static vChewing::UserOverrideModel gUserOverrideModelCHS(kUserOverrideModelCapacity, kObservedOverrideHalflife); - -@implementation mgrLangModel - -// 這個函數無法遷移至 Swift -static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing::LMInstantiator &lm) -{ - NSString *dataPath = [mgrLangModel getBundleDataPath:filenameWithoutExtension]; - lm.loadLanguageModel([dataPath UTF8String]); -} - -// 這個函數無法遷移至 Swift -+ (void)loadDataModels -{ - if (!gLangModelCHT.isDataModelLoaded()) - LTLoadLanguageModelFile(@"data-cht", gLangModelCHT); - if (!gLangModelCHT.isMiscDataLoaded()) - gLangModelCHT.loadMiscData([[self getBundleDataPath:@"data-zhuyinwen"] UTF8String]); - if (!gLangModelCHT.isSymbolDataLoaded()) - gLangModelCHT.loadSymbolData([[self getBundleDataPath:@"data-symbols"] UTF8String]); - if (!gLangModelCHT.isCNSDataLoaded()) - gLangModelCHT.loadCNSData([[self getBundleDataPath:@"char-kanji-cns"] UTF8String]); - - // ----------------- - if (!gLangModelCHS.isDataModelLoaded()) - LTLoadLanguageModelFile(@"data-chs", gLangModelCHS); - if (!gLangModelCHS.isMiscDataLoaded()) - gLangModelCHS.loadMiscData([[self getBundleDataPath:@"data-zhuyinwen"] UTF8String]); - if (!gLangModelCHS.isSymbolDataLoaded()) - gLangModelCHS.loadSymbolData([[self getBundleDataPath:@"data-symbols"] UTF8String]); - if (!gLangModelCHS.isCNSDataLoaded()) - gLangModelCHS.loadCNSData([[self getBundleDataPath:@"char-kanji-cns"] UTF8String]); -} - -// 這個函數無法遷移至 Swift -+ (void)loadDataModel:(InputMode)mode -{ - if ([mode isEqualToString:imeModeCHT]) - { - if (!gLangModelCHT.isDataModelLoaded()) - LTLoadLanguageModelFile(@"data-cht", gLangModelCHT); - if (!gLangModelCHT.isMiscDataLoaded()) - gLangModelCHT.loadMiscData([[self getBundleDataPath:@"data-zhuyinwen"] UTF8String]); - if (!gLangModelCHT.isSymbolDataLoaded()) - gLangModelCHT.loadSymbolData([[self getBundleDataPath:@"data-symbols"] UTF8String]); - if (!gLangModelCHT.isCNSDataLoaded()) - gLangModelCHT.loadCNSData([[self getBundleDataPath:@"char-kanji-cns"] UTF8String]); - } - - if ([mode isEqualToString:imeModeCHS]) - { - if (!gLangModelCHS.isDataModelLoaded()) - LTLoadLanguageModelFile(@"data-chs", gLangModelCHS); - if (!gLangModelCHS.isMiscDataLoaded()) - gLangModelCHS.loadMiscData([[self getBundleDataPath:@"data-zhuyinwen"] UTF8String]); - if (!gLangModelCHS.isSymbolDataLoaded()) - gLangModelCHS.loadSymbolData([[self getBundleDataPath:@"data-symbols"] UTF8String]); - if (!gLangModelCHS.isCNSDataLoaded()) - gLangModelCHS.loadCNSData([[self getBundleDataPath:@"char-kanji-cns"] UTF8String]); - } -} - -// 這個函數無法遷移至 Swift -+ (void)loadUserPhrases -{ - gLangModelCHT.loadUserPhrases([[self userPhrasesDataPath:imeModeCHT] UTF8String], - [[self excludedPhrasesDataPath:imeModeCHT] UTF8String]); - gLangModelCHS.loadUserPhrases([[self userPhrasesDataPath:imeModeCHS] UTF8String], - [[self excludedPhrasesDataPath:imeModeCHS] UTF8String]); - gLangModelCHT.loadUserSymbolData([[self userSymbolDataPath:imeModeCHT] UTF8String]); - gLangModelCHS.loadUserSymbolData([[self userSymbolDataPath:imeModeCHS] UTF8String]); -} - -// 這個函數無法遷移至 Swift -+ (void)loadUserAssociatedPhrases -{ - gLangModelCHT.loadUserAssociatedPhrases([[self userAssociatedPhrasesDataPath:imeModeCHT] UTF8String]); - gLangModelCHS.loadUserAssociatedPhrases([[self userAssociatedPhrasesDataPath:imeModeCHS] UTF8String]); -} - -// 這個函數無法遷移至 Swift -+ (void)loadUserPhraseReplacement -{ - gLangModelCHT.loadPhraseReplacementMap([[self phraseReplacementDataPath:imeModeCHT] UTF8String]); - gLangModelCHS.loadPhraseReplacementMap([[self phraseReplacementDataPath:imeModeCHS] UTF8String]); -} - -// 這個函數無法遷移至 Swift -+ (BOOL)checkIfUserPhraseExist:(NSString *)userPhrase - inputMode:(InputMode)mode - key:(NSString *)key NS_SWIFT_NAME(checkIfUserPhraseExist(userPhrase:mode:key:)) -{ - string unigramKey = string(key.UTF8String); - vector unigrams = [mode isEqualToString:imeModeCHT] ? gLangModelCHT.unigramsForKey(unigramKey) - : gLangModelCHS.unigramsForKey(unigramKey); - string userPhraseString = string(userPhrase.UTF8String); - for (auto unigram : unigrams) - { - if (unigram.keyValue.value == userPhraseString) - { - return YES; - } - } - return NO; -} - -// 這個函數無法遷移至 Swift -+ (void)consolidateGivenFile:(NSString *)path shouldCheckPragma:(BOOL)shouldCheckPragma -{ - vChewing::LMConsolidator::ConsolidateContent([path UTF8String], shouldCheckPragma); -} - -// 這個函數無法遷移至 Swift -+ (vChewing::LMInstantiator *)lmCHT -{ - return &gLangModelCHT; -} - -// 這個函數無法遷移至 Swift -+ (vChewing::LMInstantiator *)lmCHS -{ - return &gLangModelCHS; -} - -// 這個函數無法遷移至 Swift -+ (vChewing::UserOverrideModel *)userOverrideModelCHT -{ - return &gUserOverrideModelCHT; -} - -// 這個函數無法遷移至 Swift -+ (vChewing::UserOverrideModel *)userOverrideModelCHS -{ - return &gUserOverrideModelCHS; -} - -// 這個函數無法遷移至 Swift -+ (void)setPhraseReplacementEnabled:(BOOL)phraseReplacementEnabled -{ - gLangModelCHT.setPhraseReplacementEnabled(phraseReplacementEnabled); - gLangModelCHS.setPhraseReplacementEnabled(phraseReplacementEnabled); -} - -// 這個函數無法遷移至 Swift -+ (void)setCNSEnabled:(BOOL)cnsEnabled -{ - gLangModelCHT.setCNSEnabled(cnsEnabled); - gLangModelCHS.setCNSEnabled(cnsEnabled); -} - -// 這個函數無法遷移至 Swift -+ (void)setSymbolEnabled:(BOOL)symbolEnabled -{ - gLangModelCHT.setSymbolEnabled(symbolEnabled); - gLangModelCHS.setSymbolEnabled(symbolEnabled); -} - -@end diff --git a/Source/Modules/LangModelRelated/mgrLangModel.swift b/Source/Modules/LangModelRelated/mgrLangModel.swift index 7e4df687..8568cf62 100644 --- a/Source/Modules/LangModelRelated/mgrLangModel.swift +++ b/Source/Modules/LangModelRelated/mgrLangModel.swift @@ -26,222 +26,414 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import Cocoa -@objc extension mgrLangModel { - // MARK: - 獲取當前輸入法封包內的原廠核心語彙檔案所在路徑 +/// 我們不能讓 mgrLangModel 這個靜態管理器來承載下面這些副本變數。 +/// 所以,這些副本變數只能放在 mgrLangModel 的外部。 +/// 同時,這些變數不對外開放任意存取權限。 +/// 我們只在 mgrLangModel 內部寫幾個回傳函數、供其餘控制模組來讀取。 - static func getBundleDataPath(_ filenameSansExt: String) -> String { - Bundle.main.path(forResource: filenameSansExt, ofType: "txt")! - } +private var gLangModelCHS = vChewing.LMInstantiator() +private var gLangModelCHT = vChewing.LMInstantiator() +private var gUserOverrideModelCHS = vChewing.LMUserOverride() +private var gUserOverrideModelCHT = vChewing.LMUserOverride() - // MARK: - 使用者語彙檔案的具體檔案名稱路徑定義 +class mgrLangModel: NSObject { + /// 寫幾個回傳函數、供其餘控制模組來讀取那些被設為 fileprivate 的器外變數。 + public static var lmCHS: vChewing.LMInstantiator { gLangModelCHS } + public static var lmCHT: vChewing.LMInstantiator { gLangModelCHT } + public static var uomCHS: vChewing.LMUserOverride { gUserOverrideModelCHS } + public static var uomCHT: vChewing.LMUserOverride { gUserOverrideModelCHT } - // Swift 的 appendingPathComponent 需要藉由 URL 完成,最後再用 .path 轉為路徑。 + // MARK: - Functions reacting directly with language models. - static func userPhrasesDataPath(_ mode: InputMode) -> String { - let fileName = (mode == InputMode.imeModeCHT) ? "userdata-cht.txt" : "userdata-chs.txt" - return URL(fileURLWithPath: dataFolderPath(isDefaultFolder: false)).appendingPathComponent(fileName).path - } + static func loadCoreLanguageModelFile(filenameSansExtension: String, langModel lm: inout vChewing.LMInstantiator) { + let dataPath: String = mgrLangModel.getBundleDataPath(filenameSansExtension) + lm.loadLanguageModel(path: dataPath) + } - static func userSymbolDataPath(_ mode: InputMode) -> String { - let fileName = (mode == InputMode.imeModeCHT) ? "usersymbolphrases-cht.txt" : "usersymbolphrases-chs.txt" - return URL(fileURLWithPath: dataFolderPath(isDefaultFolder: false)).appendingPathComponent(fileName).path - } + public static func loadDataModels() { + DispatchQueue.global(qos: .userInitiated).async { + if !gLangModelCHT.isCNSDataLoaded() { + gLangModelCHT.loadCNSData(path: getBundleDataPath("char-kanji-cns")) + } + if !gLangModelCHT.isMiscDataLoaded() { + gLangModelCHT.loadMiscData(path: getBundleDataPath("data-zhuyinwen")) + } + if !gLangModelCHT.isSymbolDataLoaded() { + gLangModelCHT.loadSymbolData(path: getBundleDataPath("data-symbols")) + } + if !gLangModelCHS.isCNSDataLoaded() { + gLangModelCHS.loadCNSData(path: getBundleDataPath("char-kanji-cns")) + } + if !gLangModelCHS.isMiscDataLoaded() { + gLangModelCHS.loadMiscData(path: getBundleDataPath("data-zhuyinwen")) + } + if !gLangModelCHS.isSymbolDataLoaded() { + gLangModelCHS.loadSymbolData(path: getBundleDataPath("data-symbols")) + } + } + if !gLangModelCHT.isDataModelLoaded() { + NotifierController.notify( + message: String( + format: "%@", NSLocalizedString("Loading CHT Core Dict...", comment: "") + ) + ) + loadCoreLanguageModelFile(filenameSansExtension: "data-cht", langModel: &gLangModelCHT) + NotifierController.notify( + message: String( + format: "%@", NSLocalizedString("Core Dict loading complete.", comment: "") + ) + ) + } + if !gLangModelCHS.isDataModelLoaded() { + NotifierController.notify( + message: String( + format: "%@", NSLocalizedString("Loading CHS Core Dict...", comment: "") + ) + ) + loadCoreLanguageModelFile(filenameSansExtension: "data-chs", langModel: &gLangModelCHS) + NotifierController.notify( + message: String( + format: "%@", NSLocalizedString("Core Dict loading complete.", comment: "") + ) + ) + } + } - static func userAssociatedPhrasesDataPath(_ mode: InputMode) -> String { - let fileName = (mode == InputMode.imeModeCHT) ? "associatedPhrases-cht.txt" : "associatedPhrases-chs.txt" - return URL(fileURLWithPath: dataFolderPath(isDefaultFolder: false)).appendingPathComponent(fileName).path - } + public static func loadDataModel(_ mode: InputMode) { + if mode == InputMode.imeModeCHS { + DispatchQueue.global(qos: .userInitiated).async { + if !gLangModelCHS.isMiscDataLoaded() { + gLangModelCHS.loadMiscData(path: getBundleDataPath("data-zhuyinwen")) + } + if !gLangModelCHS.isSymbolDataLoaded() { + gLangModelCHS.loadSymbolData(path: getBundleDataPath("data-symbols")) + } + if !gLangModelCHS.isCNSDataLoaded() { + gLangModelCHS.loadCNSData(path: getBundleDataPath("char-kanji-cns")) + } + } + if !gLangModelCHS.isDataModelLoaded() { + NotifierController.notify( + message: String( + format: "%@", NSLocalizedString("Loading CHS Core Dict...", comment: "") + ) + ) + loadCoreLanguageModelFile(filenameSansExtension: "data-chs", langModel: &gLangModelCHS) + NotifierController.notify( + message: String( + format: "%@", NSLocalizedString("Core Dict loading complete.", comment: "") + ) + ) + } + } else if mode == InputMode.imeModeCHT { + DispatchQueue.global(qos: .userInitiated).async { + if !gLangModelCHT.isMiscDataLoaded() { + gLangModelCHT.loadMiscData(path: getBundleDataPath("data-zhuyinwen")) + } + if !gLangModelCHT.isSymbolDataLoaded() { + gLangModelCHT.loadSymbolData(path: getBundleDataPath("data-symbols")) + } + if !gLangModelCHT.isCNSDataLoaded() { + gLangModelCHT.loadCNSData(path: getBundleDataPath("char-kanji-cns")) + } + } + if !gLangModelCHT.isDataModelLoaded() { + NotifierController.notify( + message: String( + format: "%@", NSLocalizedString("Loading CHT Core Dict...", comment: "") + ) + ) + loadCoreLanguageModelFile(filenameSansExtension: "data-cht", langModel: &gLangModelCHT) + NotifierController.notify( + message: String( + format: "%@", NSLocalizedString("Core Dict loading complete.", comment: "") + ) + ) + } + } + } - static func excludedPhrasesDataPath(_ mode: InputMode) -> String { - let fileName = (mode == InputMode.imeModeCHT) ? "exclude-phrases-cht.txt" : "exclude-phrases-chs.txt" - return URL(fileURLWithPath: dataFolderPath(isDefaultFolder: false)).appendingPathComponent(fileName).path - } + public static func loadUserPhrases() { + gLangModelCHT.loadUserPhrases( + path: userPhrasesDataPath(InputMode.imeModeCHT), + filterPath: excludedPhrasesDataPath(InputMode.imeModeCHT) + ) + gLangModelCHS.loadUserPhrases( + path: userPhrasesDataPath(InputMode.imeModeCHS), + filterPath: excludedPhrasesDataPath(InputMode.imeModeCHS) + ) + gLangModelCHT.loadUserSymbolData(path: userSymbolDataPath(InputMode.imeModeCHT)) + gLangModelCHS.loadUserSymbolData(path: userSymbolDataPath(InputMode.imeModeCHS)) + } - static func phraseReplacementDataPath(_ mode: InputMode) -> String { - let fileName = (mode == InputMode.imeModeCHT) ? "phrases-replacement-cht.txt" : "phrases-replacement-chs.txt" - return URL(fileURLWithPath: dataFolderPath(isDefaultFolder: false)).appendingPathComponent(fileName).path - } + public static func loadUserAssociatedPhrases() { + gLangModelCHT.loadUserAssociatedPhrases( + path: mgrLangModel.userAssociatedPhrasesDataPath(InputMode.imeModeCHT) + ) + gLangModelCHS.loadUserAssociatedPhrases( + path: mgrLangModel.userAssociatedPhrasesDataPath(InputMode.imeModeCHS) + ) + } - // MARK: - 檢查具體的使用者語彙檔案是否存在 + public static func loadUserPhraseReplacement() { + gLangModelCHT.loadPhraseReplacementMap( + path: mgrLangModel.phraseReplacementDataPath(InputMode.imeModeCHT) + ) + gLangModelCHS.loadPhraseReplacementMap( + path: mgrLangModel.phraseReplacementDataPath(InputMode.imeModeCHS) + ) + } - static func ensureFileExists( - _ filePath: String, populateWithTemplate templateBasename: String = "1145141919810", - extension ext: String = "txt" - ) -> Bool { - if !FileManager.default.fileExists(atPath: filePath) { - let templateURL = Bundle.main.url(forResource: templateBasename, withExtension: ext) - var templateData = Data("".utf8) - if templateBasename != "" { - do { - try templateData = Data(contentsOf: templateURL ?? URL(fileURLWithPath: "")) - } catch { - templateData = Data("".utf8) - } - do { - try templateData.write(to: URL(fileURLWithPath: filePath)) - } catch { - IME.prtDebugIntel("Failed to write file") - return false - } - } - } - return true - } + public static func checkIfUserPhraseExist( + userPhrase: String, + mode: InputMode, + key unigramKey: String + ) -> Bool { + let unigrams: [Megrez.Unigram] = + (mode == InputMode.imeModeCHT) + ? gLangModelCHT.unigramsFor(key: unigramKey) : gLangModelCHS.unigramsFor(key: unigramKey) + for unigram in unigrams { + if unigram.keyValue.value == userPhrase { + return true + } + } + return false + } - static func chkUserLMFilesExist(_ mode: InputMode) -> Bool { - if !checkIfUserDataFolderExists() { - return false - } - if !ensureFileExists(userPhrasesDataPath(mode)) - || !ensureFileExists(userAssociatedPhrasesDataPath(mode)) - || !ensureFileExists(excludedPhrasesDataPath(mode)) - || !ensureFileExists(phraseReplacementDataPath(mode)) - || !ensureFileExists(userSymbolDataPath(mode)) - { - return false - } + public static func setPhraseReplacementEnabled(_ state: Bool) { + gLangModelCHT.isPhraseReplacementEnabled = state + gLangModelCHS.isPhraseReplacementEnabled = state + } - return true - } + public static func setCNSEnabled(_ state: Bool) { + gLangModelCHT.isCNSEnabled = state + gLangModelCHS.isCNSEnabled = state + } - // MARK: - 使用者語彙檔案專用目錄的合規性檢查 + public static func setSymbolEnabled(_ state: Bool) { + gLangModelCHT.isSymbolEnabled = state + gLangModelCHS.isSymbolEnabled = state + } - // 一次性檢查給定的目錄是否存在寫入合規性(僅用於偏好設定檢查等初步檢查場合,不做任何糾偏行為) - static func checkIfSpecifiedUserDataFolderValid(_ folderPath: String?) -> Bool { - var isFolder = ObjCBool(false) - let folderExist = FileManager.default.fileExists(atPath: folderPath ?? "", isDirectory: &isFolder) - // The above "&" mutates the "isFolder" value to the real one received by the "folderExist". + // MARK: - 獲取當前輸入法封包內的原廠核心語彙檔案所在路徑 - // 路徑沒有結尾斜槓的話,會導致目錄合規性判定失準。 - // 出於每個型別每個函數的自我責任原則,這裡多檢查一遍也不壞。 - var folderPath = folderPath // Convert the incoming constant to a variable. - if isFolder.boolValue { - folderPath?.ensureTrailingSlash() - } - let isFolderWritable = FileManager.default.isWritableFile(atPath: folderPath ?? "") + static func getBundleDataPath(_ filenameSansExt: String) -> String { + Bundle.main.path(forResource: filenameSansExt, ofType: "txt")! + } - if ((folderExist && !isFolder.boolValue) || !folderExist) || !isFolderWritable { - return false - } + // MARK: - 使用者語彙檔案的具體檔案名稱路徑定義 - return true - } + // Swift 的 appendingPathComponent 需要藉由 URL 完成,最後再用 .path 轉為路徑。 - // ⚠︎ 私有函數:檢查且糾偏,不接受任何傳入變數。該函數不用於其他型別。 - // 待辦事項:擇日合併至另一個同類型的函數當中。 - static func checkIfUserDataFolderExists() -> Bool { - let folderPath = mgrLangModel.dataFolderPath(isDefaultFolder: false) - var isFolder = ObjCBool(false) - var folderExist = FileManager.default.fileExists(atPath: folderPath, isDirectory: &isFolder) - // The above "&" mutates the "isFolder" value to the real one received by the "folderExist". - // 發現目標路徑不是目錄的話: - // 如果要找的目標路徑是原廠目標路徑的話,先將這個路徑的所指對象更名、再認為目錄不存在。 - // 如果要找的目標路徑不是原廠目標路徑的話,則直接報錯。 - if folderExist, !isFolder.boolValue { - do { - if dataFolderPath(isDefaultFolder: false) - == dataFolderPath(isDefaultFolder: true) - { - let formatter = DateFormatter() - formatter.dateFormat = "YYYYMMDD-HHMM'Hrs'-ss's'" - let dirAlternative = folderPath + formatter.string(from: Date()) - try FileManager.default.moveItem(atPath: folderPath, toPath: dirAlternative) - } else { - throw folderPath - } - } catch { - print("Failed to make path available at: \(error)") - return false - } - folderExist = false - } - if !folderExist { - do { - try FileManager.default.createDirectory( - atPath: folderPath, - withIntermediateDirectories: true, - attributes: nil - ) - } catch { - print("Failed to create folder: \(error)") - return false - } - } - return true - } + static func userPhrasesDataPath(_ mode: InputMode) -> String { + let fileName = (mode == InputMode.imeModeCHT) ? "userdata-cht.txt" : "userdata-chs.txt" + return URL(fileURLWithPath: dataFolderPath(isDefaultFolder: false)).appendingPathComponent(fileName).path + } - // MARK: - 用以讀取使用者語彙檔案目錄的函數,會自動對 mgrPrefs 當中的參數糾偏。 + static func userSymbolDataPath(_ mode: InputMode) -> String { + let fileName = (mode == InputMode.imeModeCHT) ? "usersymbolphrases-cht.txt" : "usersymbolphrases-chs.txt" + return URL(fileURLWithPath: dataFolderPath(isDefaultFolder: false)).appendingPathComponent(fileName).path + } - // 當且僅當 mgrPrefs 當中的參數不合規(比如非實在路徑、或者無權限寫入)時,才會糾偏。 + static func userAssociatedPhrasesDataPath(_ mode: InputMode) -> String { + let fileName = (mode == InputMode.imeModeCHT) ? "associatedPhrases-cht.txt" : "associatedPhrases-chs.txt" + return URL(fileURLWithPath: dataFolderPath(isDefaultFolder: false)).appendingPathComponent(fileName).path + } - static func dataFolderPath(isDefaultFolder: Bool) -> String { - let appSupportPath = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0].path - var userDictPathSpecified = (mgrPrefs.userDataFolderSpecified as NSString).expandingTildeInPath - var userDictPathDefault = - (URL(fileURLWithPath: appSupportPath).appendingPathComponent("vChewing").path as NSString) - .expandingTildeInPath + static func excludedPhrasesDataPath(_ mode: InputMode) -> String { + let fileName = (mode == InputMode.imeModeCHT) ? "exclude-phrases-cht.txt" : "exclude-phrases-chs.txt" + return URL(fileURLWithPath: dataFolderPath(isDefaultFolder: false)).appendingPathComponent(fileName).path + } - userDictPathDefault.ensureTrailingSlash() - userDictPathSpecified.ensureTrailingSlash() + static func phraseReplacementDataPath(_ mode: InputMode) -> String { + let fileName = (mode == InputMode.imeModeCHT) ? "phrases-replacement-cht.txt" : "phrases-replacement-chs.txt" + return URL(fileURLWithPath: dataFolderPath(isDefaultFolder: false)).appendingPathComponent(fileName).path + } - if (userDictPathSpecified == userDictPathDefault) - || isDefaultFolder - { - return userDictPathDefault - } - if mgrPrefs.ifSpecifiedUserDataPathExistsInPlist() { - if mgrLangModel.checkIfSpecifiedUserDataFolderValid(userDictPathSpecified) { - return userDictPathSpecified - } else { - UserDefaults.standard.removeObject(forKey: "UserDataFolderSpecified") - } - } - return userDictPathDefault - } + // MARK: - 檢查具體的使用者語彙檔案是否存在 - // MARK: - 寫入使用者檔案 + static func ensureFileExists( + _ filePath: String, populateWithTemplate templateBasename: String = "1145141919810", + extension ext: String = "txt" + ) -> Bool { + if !FileManager.default.fileExists(atPath: filePath) { + let templateURL = Bundle.main.url(forResource: templateBasename, withExtension: ext) + var templateData = Data("".utf8) + if templateBasename != "" { + do { + try templateData = Data(contentsOf: templateURL ?? URL(fileURLWithPath: "")) + } catch { + templateData = Data("".utf8) + } + do { + try templateData.write(to: URL(fileURLWithPath: filePath)) + } catch { + IME.prtDebugIntel("Failed to write template data to: \(filePath)") + return false + } + } + } + return true + } - static func writeUserPhrase( - _ userPhrase: String?, inputMode mode: InputMode, areWeDuplicating: Bool, areWeDeleting: Bool - ) -> Bool { - if var currentMarkedPhrase: String = userPhrase { - if !chkUserLMFilesExist(InputMode.imeModeCHS) - || !chkUserLMFilesExist(InputMode.imeModeCHT) - { - return false - } + static func chkUserLMFilesExist(_ mode: InputMode) -> Bool { + if !checkIfUserDataFolderExists() { + return false + } + if !ensureFileExists(userPhrasesDataPath(mode)) + || !ensureFileExists(userAssociatedPhrasesDataPath(mode)) + || !ensureFileExists(excludedPhrasesDataPath(mode)) + || !ensureFileExists(phraseReplacementDataPath(mode)) + || !ensureFileExists(userSymbolDataPath(mode)) + { + return false + } - let path = areWeDeleting ? excludedPhrasesDataPath(mode) : userPhrasesDataPath(mode) + return true + } - if areWeDuplicating, !areWeDeleting { - // Do not use ASCII characters to comment here. - // Otherwise, it will be scrambled by cnvHYPYtoBPMF - // module shipped in the vChewing Phrase Editor. - currentMarkedPhrase += "\t#𝙾𝚟𝚎𝚛𝚛𝚒𝚍𝚎" - } - currentMarkedPhrase += "\n" + // MARK: - 使用者語彙檔案專用目錄的合規性檢查 - if let writeFile = FileHandle(forUpdatingAtPath: path), - let data = currentMarkedPhrase.data(using: .utf8) - { - writeFile.seekToEndOfFile() - writeFile.write(data) - writeFile.closeFile() - } else { - return false - } + // 一次性檢查給定的目錄是否存在寫入合規性(僅用於偏好設定檢查等初步檢查場合,不做任何糾偏行為) + static func checkIfSpecifiedUserDataFolderValid(_ folderPath: String?) -> Bool { + var isFolder = ObjCBool(false) + let folderExist = FileManager.default.fileExists(atPath: folderPath ?? "", isDirectory: &isFolder) + // The above "&" mutates the "isFolder" value to the real one received by the "folderExist". - // We enforce the format consolidation here, since the pragma header - // will let the UserPhraseLM bypasses the consolidating process on load. - consolidate(givenFile: path, shouldCheckPragma: false) + // 路徑沒有結尾斜槓的話,會導致目錄合規性判定失準。 + // 出於每個型別每個函數的自我責任原則,這裡多檢查一遍也不壞。 + var folderPath = folderPath // Convert the incoming constant to a variable. + if isFolder.boolValue { + folderPath?.ensureTrailingSlash() + } + let isFolderWritable = FileManager.default.isWritableFile(atPath: folderPath ?? "") - // We use FSEventStream to monitor possible changes of the user phrase folder, hence the - // lack of the needs of manually load data here unless FSEventStream is disabled by user. - if !mgrPrefs.shouldAutoReloadUserDataFiles { - loadUserPhrases() - } - return true - } - return false - } + if ((folderExist && !isFolder.boolValue) || !folderExist) || !isFolderWritable { + return false + } + + return true + } + + // ⚠︎ 私有函數:檢查且糾偏,不接受任何傳入變數。該函數不用於其他型別。 + // 待辦事項:擇日合併至另一個同類型的函數當中。 + static func checkIfUserDataFolderExists() -> Bool { + let folderPath = mgrLangModel.dataFolderPath(isDefaultFolder: false) + var isFolder = ObjCBool(false) + var folderExist = FileManager.default.fileExists(atPath: folderPath, isDirectory: &isFolder) + // The above "&" mutates the "isFolder" value to the real one received by the "folderExist". + // 發現目標路徑不是目錄的話: + // 如果要找的目標路徑是原廠目標路徑的話,先將這個路徑的所指對象更名、再認為目錄不存在。 + // 如果要找的目標路徑不是原廠目標路徑的話,則直接報錯。 + if folderExist, !isFolder.boolValue { + do { + if dataFolderPath(isDefaultFolder: false) + == dataFolderPath(isDefaultFolder: true) + { + let formatter = DateFormatter() + formatter.dateFormat = "YYYYMMDD-HHMM'Hrs'-ss's'" + let dirAlternative = folderPath + formatter.string(from: Date()) + try FileManager.default.moveItem(atPath: folderPath, toPath: dirAlternative) + } else { + throw folderPath + } + } catch { + print("Failed to make path available at: \(error)") + return false + } + folderExist = false + } + if !folderExist { + do { + try FileManager.default.createDirectory( + atPath: folderPath, + withIntermediateDirectories: true, + attributes: nil + ) + } catch { + print("Failed to create folder: \(error)") + return false + } + } + return true + } + + // MARK: - 用以讀取使用者語彙檔案目錄的函數,會自動對 mgrPrefs 當中的參數糾偏。 + + // 當且僅當 mgrPrefs 當中的參數不合規(比如非實在路徑、或者無權限寫入)時,才會糾偏。 + + static func dataFolderPath(isDefaultFolder: Bool) -> String { + let appSupportPath = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0].path + var userDictPathSpecified = (mgrPrefs.userDataFolderSpecified as NSString).expandingTildeInPath + var userDictPathDefault = + (URL(fileURLWithPath: appSupportPath).appendingPathComponent("vChewing").path as NSString) + .expandingTildeInPath + + userDictPathDefault.ensureTrailingSlash() + userDictPathSpecified.ensureTrailingSlash() + + if (userDictPathSpecified == userDictPathDefault) + || isDefaultFolder + { + return userDictPathDefault + } + if mgrPrefs.ifSpecifiedUserDataPathExistsInPlist() { + if mgrLangModel.checkIfSpecifiedUserDataFolderValid(userDictPathSpecified) { + return userDictPathSpecified + } else { + UserDefaults.standard.removeObject(forKey: "UserDataFolderSpecified") + } + } + return userDictPathDefault + } + + // MARK: - 寫入使用者檔案 + + static func writeUserPhrase( + _ userPhrase: String?, inputMode mode: InputMode, areWeDuplicating: Bool, areWeDeleting: Bool + ) -> Bool { + if var currentMarkedPhrase: String = userPhrase { + if !chkUserLMFilesExist(InputMode.imeModeCHS) + || !chkUserLMFilesExist(InputMode.imeModeCHT) + { + return false + } + + let path = areWeDeleting ? excludedPhrasesDataPath(mode) : userPhrasesDataPath(mode) + + if areWeDuplicating, !areWeDeleting { + // Do not use ASCII characters to comment here. + // Otherwise, it will be scrambled by cnvHYPYtoBPMF + // module shipped in the vChewing Phrase Editor. + currentMarkedPhrase += "\t#𝙾𝚟𝚎𝚛𝚛𝚒𝚍𝚎" + } + + if let writeFile = FileHandle(forUpdatingAtPath: path), + let data = currentMarkedPhrase.data(using: .utf8), + let endl = "\n".data(using: .utf8) + { + writeFile.seekToEndOfFile() + writeFile.write(endl) + writeFile.write(data) + writeFile.write(endl) + writeFile.closeFile() + } else { + return false + } + + // We enforce the format consolidation here, since the pragma header + // will let the UserPhraseLM bypasses the consolidating process on load. + if !vChewing.LMConsolidator.consolidate(path: path, pragma: false) { + return false + } + + // We use FSEventStream to monitor possible changes of the user phrase folder, hence the + // lack of the needs of manually load data here unless FSEventStream is disabled by user. + if !mgrPrefs.shouldAutoReloadUserDataFiles { + loadUserPhrases() + } + return true + } + return false + } } diff --git a/Source/Modules/LangModelRelated/mgrLangModel_Privates.h b/Source/Modules/LangModelRelated/mgrLangModel_Privates.h deleted file mode 100644 index cc42ca2a..00000000 --- a/Source/Modules/LangModelRelated/mgrLangModel_Privates.h +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are of: -// (c) 2021 and onwards The vChewing Project (MIT-NTL License). -/* -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -1. The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -2. No trademark license is granted to use the trade names, trademarks, service -marks, or product names of Contributor, except as required to fulfill notice -requirements above. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -#import "LMInstantiator.h" -#import "UserOverrideModel.h" -#import "mgrLangModel.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface mgrLangModel () -@property(class, readonly, nonatomic) vChewing::LMInstantiator *lmCHT; -@property(class, readonly, nonatomic) vChewing::LMInstantiator *lmCHS; -@property(class, readonly, nonatomic) vChewing::UserOverrideModel *userOverrideModelCHS; -@property(class, readonly, nonatomic) vChewing::UserOverrideModel *userOverrideModelCHT; -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/Modules/LanguageParsers/Gramambular/Bigram.h b/Source/Modules/LanguageParsers/Gramambular/Bigram.h deleted file mode 100644 index a4b8c8b2..00000000 --- a/Source/Modules/LanguageParsers/Gramambular/Bigram.h +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are of: -// (c) 2021 and onwards The vChewing Project (MIT-NTL License). -/* -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -1. The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -2. No trademark license is granted to use the trade names, trademarks, service -marks, or product names of Contributor, except as required to fulfill notice -requirements above. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -#ifndef BIGRAM_H_ -#define BIGRAM_H_ - -#include - -#include "KeyValuePair.h" - -namespace Gramambular -{ -class Bigram -{ - public: - Bigram(); - - KeyValuePair preceedingKeyValue; - KeyValuePair keyValue; - double score; - - bool operator==(const Bigram &another) const; - bool operator<(const Bigram &another) const; -}; - -inline std::ostream &operator<<(std::ostream &stream, const Bigram &gram) -{ - std::streamsize p = stream.precision(); - stream.precision(6); - stream << "(" << gram.keyValue << "|" << gram.preceedingKeyValue << "," << gram.score << ")"; - stream.precision(p); - return stream; -} - -inline std::ostream &operator<<(std::ostream &stream, const std::vector &grams) -{ - stream << "[" << grams.size() << "]=>{"; - - size_t index = 0; - - for (std::vector::const_iterator gi = grams.begin(); gi != grams.end(); ++gi, ++index) - { - stream << index << "=>"; - stream << *gi; - if (gi + 1 != grams.end()) - { - stream << ","; - } - } - - stream << "}"; - return stream; -} - -inline Bigram::Bigram() : score(0.0) -{ -} - -inline bool Bigram::operator==(const Bigram &another) const -{ - return preceedingKeyValue == another.preceedingKeyValue && keyValue == another.keyValue && score == another.score; -} - -inline bool Bigram::operator<(const Bigram &another) const -{ - if (preceedingKeyValue < another.preceedingKeyValue) - { - return true; - } - else if (preceedingKeyValue == another.preceedingKeyValue) - { - if (keyValue < another.keyValue) - { - return true; - } - else if (keyValue == another.keyValue) - { - return score < another.score; - } - return false; - } - - return false; -} -} // namespace Gramambular - -#endif diff --git a/Source/Modules/LanguageParsers/Gramambular/BlockReadingBuilder.h b/Source/Modules/LanguageParsers/Gramambular/BlockReadingBuilder.h deleted file mode 100644 index 12046b15..00000000 --- a/Source/Modules/LanguageParsers/Gramambular/BlockReadingBuilder.h +++ /dev/null @@ -1,242 +0,0 @@ -// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are of: -// (c) 2021 and onwards The vChewing Project (MIT-NTL License). -/* -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -1. The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -2. No trademark license is granted to use the trade names, trademarks, service -marks, or product names of Contributor, except as required to fulfill notice -requirements above. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -#ifndef BLOCKREADINGBUILDER_H_ -#define BLOCKREADINGBUILDER_H_ - -#include -#include - -#include "Grid.h" -#include "LanguageModel.h" - -namespace Gramambular -{ - -class BlockReadingBuilder -{ - public: - explicit BlockReadingBuilder(LanguageModel *lm); - void clear(); - - size_t length() const; - size_t cursorIndex() const; - void setCursorIndex(size_t newIndex); - void insertReadingAtCursor(const std::string &reading); - bool deleteReadingBeforeCursor(); // backspace - bool deleteReadingAfterCursor(); // delete - - bool removeHeadReadings(size_t count); - - void setJoinSeparator(const std::string &separator); - const std::string joinSeparator() const; - - std::vector readings() const; - - Grid &grid(); - - protected: - void build(); - - static const std::string Join(std::vector::const_iterator begin, - std::vector::const_iterator end, const std::string &separator); - - // 規定最多可以組成的詞的字數上限為 10 - static const size_t MaximumBuildSpanLength = 10; - - size_t m_cursorIndex; - std::vector m_readings; - - Grid m_grid; - LanguageModel *m_LM; - std::string m_joinSeparator; -}; - -inline BlockReadingBuilder::BlockReadingBuilder(LanguageModel *lm) : m_LM(lm), m_cursorIndex(0) -{ -} - -inline void BlockReadingBuilder::clear() -{ - m_cursorIndex = 0; - m_readings.clear(); - m_grid.clear(); -} - -inline size_t BlockReadingBuilder::length() const -{ - return m_readings.size(); -} - -inline size_t BlockReadingBuilder::cursorIndex() const -{ - return m_cursorIndex; -} - -inline void BlockReadingBuilder::setCursorIndex(size_t newIndex) -{ - m_cursorIndex = newIndex > m_readings.size() ? m_readings.size() : newIndex; -} - -inline void BlockReadingBuilder::insertReadingAtCursor(const std::string &reading) -{ - m_readings.insert(m_readings.begin() + m_cursorIndex, reading); - - m_grid.expandGridByOneAtLocation(m_cursorIndex); - build(); - m_cursorIndex++; -} - -inline std::vector BlockReadingBuilder::readings() const -{ - return m_readings; -} - -inline bool BlockReadingBuilder::deleteReadingBeforeCursor() -{ - if (!m_cursorIndex) - { - return false; - } - - m_readings.erase(m_readings.begin() + m_cursorIndex - 1, m_readings.begin() + m_cursorIndex); - m_cursorIndex--; - m_grid.shrinkGridByOneAtLocation(m_cursorIndex); - build(); - return true; -} - -inline bool BlockReadingBuilder::deleteReadingAfterCursor() -{ - if (m_cursorIndex == m_readings.size()) - { - return false; - } - - m_readings.erase(m_readings.begin() + m_cursorIndex, m_readings.begin() + m_cursorIndex + 1); - m_grid.shrinkGridByOneAtLocation(m_cursorIndex); - build(); - return true; -} - -inline bool BlockReadingBuilder::removeHeadReadings(size_t count) -{ - if (count > length()) - { - return false; - } - - for (size_t i = 0; i < count; i++) - { - if (m_cursorIndex) - { - m_cursorIndex--; - } - m_readings.erase(m_readings.begin(), m_readings.begin() + 1); - m_grid.shrinkGridByOneAtLocation(0); - build(); - } - - return true; -} - -inline void BlockReadingBuilder::setJoinSeparator(const std::string &separator) -{ - m_joinSeparator = separator; -} - -inline const std::string BlockReadingBuilder::joinSeparator() const -{ - return m_joinSeparator; -} - -inline Grid &BlockReadingBuilder::grid() -{ - return m_grid; -} - -inline void BlockReadingBuilder::build() -{ - if (!m_LM) - { - return; - } - - size_t begin = 0; - size_t end = m_cursorIndex + MaximumBuildSpanLength; - - if (m_cursorIndex < MaximumBuildSpanLength) - { - begin = 0; - } - else - { - begin = m_cursorIndex - MaximumBuildSpanLength; - } - - if (end > m_readings.size()) - { - end = m_readings.size(); - } - - for (size_t p = begin; p < end; p++) - { - for (size_t q = 1; q <= MaximumBuildSpanLength && p + q <= end; q++) - { - std::string combinedReading = Join(m_readings.begin() + p, m_readings.begin() + p + q, m_joinSeparator); - if (!m_grid.hasNodeAtLocationSpanningLengthMatchingKey(p, q, combinedReading)) - { - std::vector unigrams = m_LM->unigramsForKey(combinedReading); - - if (unigrams.size() > 0) - { - Node n(combinedReading, unigrams, std::vector()); - m_grid.insertNode(n, p, q); - } - } - } - } -} - -inline const std::string BlockReadingBuilder::Join(std::vector::const_iterator begin, - std::vector::const_iterator end, - const std::string &separator) -{ - std::string result; - for (std::vector::const_iterator iter = begin; iter != end;) - { - result += *iter; - ++iter; - if (iter != end) - { - result += separator; - } - } - return result; -} -} // namespace Gramambular - -#endif diff --git a/Source/Modules/LanguageParsers/Gramambular/Grid.h b/Source/Modules/LanguageParsers/Gramambular/Grid.h deleted file mode 100644 index 5a39fe7a..00000000 --- a/Source/Modules/LanguageParsers/Gramambular/Grid.h +++ /dev/null @@ -1,313 +0,0 @@ -// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are of: -// (c) 2021 and onwards The vChewing Project (MIT-NTL License). -/* -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -1. The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -2. No trademark license is granted to use the trade names, trademarks, service -marks, or product names of Contributor, except as required to fulfill notice -requirements above. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -#ifndef GRID_H_ -#define GRID_H_ - -#include -#include -#include - -#include "NodeAnchor.h" -#include "Span.h" - -namespace Gramambular -{ - -class Grid -{ - public: - void clear(); - void insertNode(const Node &node, size_t location, size_t spanningLength); - bool hasNodeAtLocationSpanningLengthMatchingKey(size_t location, size_t spanningLength, const std::string &key); - - void expandGridByOneAtLocation(size_t location); - void shrinkGridByOneAtLocation(size_t location); - - size_t width() const; - std::vector nodesEndingAt(size_t location); - std::vector nodesCrossingOrEndingAt(size_t location); - - // "Freeze" the node with the unigram that represents the selected candidate - // value. After this, the node that contains the unigram will always be - // evaluated to that unigram, while all other overlapping nodes will be reset - // to their initial state (that is, if any of those nodes were "frozen" or - // fixed, they will be unfrozen.) - NodeAnchor fixNodeSelectedCandidate(size_t location, const std::string &value); - - // Similar to fixNodeSelectedCandidate, but instead of "freezing" the node, - // only boost the unigram that represents the value with an overriding score. - // This has the same side effect as fixNodeSelectedCandidate, which is that - // all other overlapping nodes will be reset to their initial state. - void overrideNodeScoreForSelectedCandidate(size_t location, const std::string &value, float overridingScore); - - std::string dumpDOT() - { - std::stringstream sst; - sst << "digraph {" << std::endl; - sst << "graph [ rankdir=LR ];" << std::endl; - sst << "BOS;" << std::endl; - - for (size_t p = 0; p < m_spans.size(); p++) - { - Span &span = m_spans[p]; - for (size_t ni = 0; ni <= span.maximumLength(); ni++) - { - Node *np = span.nodeOfLength(ni); - if (np) - { - if (!p) - { - sst << "BOS -> " << np->currentKeyValue().value << ";" << std::endl; - } - - sst << np->currentKeyValue().value << ";" << std::endl; - - if (p + ni < m_spans.size()) - { - Span &dstSpan = m_spans[p + ni]; - for (size_t q = 0; q <= dstSpan.maximumLength(); q++) - { - Node *dn = dstSpan.nodeOfLength(q); - if (dn) - { - sst << np->currentKeyValue().value << " -> " << dn->currentKeyValue().value << ";" - << std::endl; - } - } - } - - if (p + ni == m_spans.size()) - { - sst << np->currentKeyValue().value << " -> " - << "EOS;" << std::endl; - } - } - } - } - - sst << "EOS;" << std::endl; - sst << "}"; - return sst.str(); - } - - protected: - std::vector m_spans; -}; - -inline void Grid::clear() -{ - m_spans.clear(); -} - -inline void Grid::insertNode(const Node &node, size_t location, size_t spanningLength) -{ - if (location >= m_spans.size()) - { - size_t diff = location - m_spans.size() + 1; - - for (size_t i = 0; i < diff; i++) - { - m_spans.push_back(Span()); - } - } - - m_spans[location].insertNodeOfLength(node, spanningLength); -} - -inline bool Grid::hasNodeAtLocationSpanningLengthMatchingKey(size_t location, size_t spanningLength, - const std::string &key) -{ - if (location > m_spans.size()) - { - return false; - } - - const Node *n = m_spans[location].nodeOfLength(spanningLength); - if (!n) - { - return false; - } - - return key == n->key(); -} - -inline void Grid::expandGridByOneAtLocation(size_t location) -{ - if (!location || location == m_spans.size()) - { - m_spans.insert(m_spans.begin() + location, Span()); - } - else - { - m_spans.insert(m_spans.begin() + location, Span()); - for (size_t i = 0; i < location; i++) - { - // zaps overlapping spans - m_spans[i].removeNodeOfLengthGreaterThan(location - i); - } - } -} - -inline void Grid::shrinkGridByOneAtLocation(size_t location) -{ - if (location >= m_spans.size()) - { - return; - } - - m_spans.erase(m_spans.begin() + location); - for (size_t i = 0; i < location; i++) - { - // zaps overlapping spans - m_spans[i].removeNodeOfLengthGreaterThan(location - i); - } -} - -inline size_t Grid::width() const -{ - return m_spans.size(); -} - -// macOS 10.6 開始的內建注音的游標前置選字風格 -inline std::vector Grid::nodesEndingAt(size_t location) -{ - std::vector result; - - if (m_spans.size() && location <= m_spans.size()) - { - for (size_t i = 0; i < location; i++) - { - Span &span = m_spans[i]; - if (i + span.maximumLength() >= location) - { - Node *np = span.nodeOfLength(location - i); - if (np) - { - NodeAnchor na; - na.node = np; - na.location = i; - na.spanningLength = location - i; - - result.push_back(na); - } - } - } - } - - return result; -} - -// Windows 版奇摩注音輸入法的游標後置的選字風格。 -// 與微軟新注音相異的是,這個風格允許在詞的中間叫出候選字窗。 -inline std::vector Grid::nodesCrossingOrEndingAt(size_t location) -{ - std::vector result; - - if (m_spans.size() && location <= m_spans.size()) - { - for (size_t i = 0; i < location; i++) - { - Span &span = m_spans[i]; - - if (i + span.maximumLength() >= location) - { - for (size_t j = 1, m = span.maximumLength(); j <= m; j++) - { - if (i + j < location) - { - continue; - } - - Node *np = span.nodeOfLength(j); - if (np) - { - NodeAnchor na; - na.node = np; - na.location = i; - na.spanningLength = location - i; - - result.push_back(na); - } - } - } - } - } - - return result; -} - -// For nodes found at the location, fix their currently-selected candidate using -// the supplied string value. -inline NodeAnchor Grid::fixNodeSelectedCandidate(size_t location, const std::string &value) -{ - std::vector nodes = nodesCrossingOrEndingAt(location); - NodeAnchor node; - for (auto nodeAnchor : nodes) - { - auto candidates = nodeAnchor.node->candidates(); - - // Reset the candidate-fixed state of every node at the location. - const_cast(nodeAnchor.node)->resetCandidate(); - - for (size_t i = 0, c = candidates.size(); i < c; ++i) - { - if (candidates[i].value == value) - { - const_cast(nodeAnchor.node)->selectCandidateAtIndex(i); - node = nodeAnchor; - break; - } - } - } - return node; -} - -inline void Grid::overrideNodeScoreForSelectedCandidate(size_t location, const std::string &value, - float overridingScore) -{ - std::vector nodes = nodesCrossingOrEndingAt(location); - for (auto nodeAnchor : nodes) - { - auto candidates = nodeAnchor.node->candidates(); - - // Reset the candidate-fixed state of every node at the location. - const_cast(nodeAnchor.node)->resetCandidate(); - - for (size_t i = 0, c = candidates.size(); i < c; ++i) - { - if (candidates[i].value == value) - { - const_cast(nodeAnchor.node)->selectFloatingCandidateAtIndex(i, overridingScore); - break; - } - } - } -} - -} // namespace Gramambular - -#endif diff --git a/Source/Modules/LanguageParsers/Gramambular/KeyValuePair.h b/Source/Modules/LanguageParsers/Gramambular/KeyValuePair.h deleted file mode 100644 index 231d6342..00000000 --- a/Source/Modules/LanguageParsers/Gramambular/KeyValuePair.h +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are of: -// (c) 2021 and onwards The vChewing Project (MIT-NTL License). -/* -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -1. The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -2. No trademark license is granted to use the trade names, trademarks, service -marks, or product names of Contributor, except as required to fulfill notice -requirements above. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -#ifndef KEYVALUEPAIR_H_ -#define KEYVALUEPAIR_H_ - -#include -#include - -namespace Gramambular -{ - -class KeyValuePair -{ - public: - std::string key; - std::string value; - - bool operator==(const KeyValuePair &another) const; - bool operator<(const KeyValuePair &another) const; -}; - -inline std::ostream &operator<<(std::ostream &stream, const KeyValuePair &pair) -{ - stream << "(" << pair.key << "," << pair.value << ")"; - return stream; -} - -inline bool KeyValuePair::operator==(const KeyValuePair &another) const -{ - return key == another.key && value == another.value; -} - -inline bool KeyValuePair::operator<(const KeyValuePair &another) const -{ - if (key < another.key) - { - return true; - } - else if (key == another.key) - { - return value < another.value; - } - return false; -} -} // namespace Gramambular - -#endif diff --git a/Source/Modules/LanguageParsers/Gramambular/Node.h b/Source/Modules/LanguageParsers/Gramambular/Node.h deleted file mode 100644 index 16b69fdf..00000000 --- a/Source/Modules/LanguageParsers/Gramambular/Node.h +++ /dev/null @@ -1,249 +0,0 @@ -// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are of: -// (c) 2021 and onwards The vChewing Project (MIT-NTL License). -/* -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -1. The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -2. No trademark license is granted to use the trade names, trademarks, service -marks, or product names of Contributor, except as required to fulfill notice -requirements above. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -#ifndef NODE_H_ -#define NODE_H_ - -#include -#include -#include -#include - -#include "LanguageModel.h" - -namespace Gramambular -{ - -class Node -{ - public: - Node(); - Node(const std::string &key, const std::vector &unigrams, const std::vector &bigrams); - - void primeNodeWithPreceedingKeyValues(const std::vector &keyValues); - - bool isCandidateFixed() const; - const std::vector &candidates() const; - void selectCandidateAtIndex(size_t index = 0, bool fix = true); - void resetCandidate(); - void selectFloatingCandidateAtIndex(size_t index, double score); - - const std::string &key() const; - double score() const; - double scoreForCandidate(const std::string &candidate) const; - const KeyValuePair currentKeyValue() const; - double highestUnigramScore() const; - - protected: - const LanguageModel *m_LM; - - std::string m_key; - double m_score; - - std::vector m_unigrams; - std::vector m_candidates; - std::map m_valueUnigramIndexMap; - std::map> m_preceedingGramBigramMap; - - bool m_candidateFixed; - size_t m_selectedUnigramIndex; - - friend std::ostream &operator<<(std::ostream &stream, const Node &node); -}; - -inline std::ostream &operator<<(std::ostream &stream, const Node &node) -{ - stream << "(node,key:" << node.m_key << ",fixed:" << (node.m_candidateFixed ? "true" : "false") - << ",selected:" << node.m_selectedUnigramIndex << "," << node.m_unigrams << ")"; - return stream; -} - -inline Node::Node() : m_candidateFixed(false), m_selectedUnigramIndex(0), m_score(0.0) -{ -} - -inline Node::Node(const std::string &key, const std::vector &unigrams, const std::vector &bigrams) - : m_key(key), m_unigrams(unigrams), m_candidateFixed(false), m_selectedUnigramIndex(0), m_score(0.0) -{ - stable_sort(m_unigrams.begin(), m_unigrams.end(), Unigram::ScoreCompare); - - if (m_unigrams.size()) - { - m_score = m_unigrams[0].score; - } - - size_t i = 0; - for (std::vector::const_iterator ui = m_unigrams.begin(); ui != m_unigrams.end(); ++ui) - { - m_valueUnigramIndexMap[(*ui).keyValue.value] = i; - i++; - - m_candidates.push_back((*ui).keyValue); - } - - for (std::vector::const_iterator bi = bigrams.begin(); bi != bigrams.end(); ++bi) - { - m_preceedingGramBigramMap[(*bi).preceedingKeyValue].push_back(*bi); - } -} - -inline void Node::primeNodeWithPreceedingKeyValues(const std::vector &keyValues) -{ - size_t newIndex = m_selectedUnigramIndex; - double max = m_score; - - if (!isCandidateFixed()) - { - for (std::vector::const_iterator kvi = keyValues.begin(); kvi != keyValues.end(); ++kvi) - { - std::map>::const_iterator f = m_preceedingGramBigramMap.find(*kvi); - if (f != m_preceedingGramBigramMap.end()) - { - const std::vector &bigrams = (*f).second; - - for (std::vector::const_iterator bi = bigrams.begin(); bi != bigrams.end(); ++bi) - { - const Bigram &bigram = *bi; - if (bigram.score > max) - { - std::map::const_iterator uf = - m_valueUnigramIndexMap.find((*bi).keyValue.value); - if (uf != m_valueUnigramIndexMap.end()) - { - newIndex = (*uf).second; - max = bigram.score; - } - } - } - } - } - } - - if (m_score != max) - { - m_score = max; - } - - if (newIndex != m_selectedUnigramIndex) - { - m_selectedUnigramIndex = newIndex; - } -} - -inline bool Node::isCandidateFixed() const -{ - return m_candidateFixed; -} - -inline const std::vector &Node::candidates() const -{ - return m_candidates; -} - -inline void Node::selectCandidateAtIndex(size_t index, bool fix) -{ - if (index >= m_unigrams.size()) - { - m_selectedUnigramIndex = 0; - } - else - { - m_selectedUnigramIndex = index; - } - - m_candidateFixed = fix; - m_score = 99; -} - -inline void Node::resetCandidate() -{ - m_selectedUnigramIndex = 0; - m_candidateFixed = 0; - if (m_unigrams.size()) - { - m_score = m_unigrams[0].score; - } -} - -inline void Node::selectFloatingCandidateAtIndex(size_t index, double score) -{ - if (index >= m_unigrams.size()) - { - m_selectedUnigramIndex = 0; - } - else - { - m_selectedUnigramIndex = index; - } - m_candidateFixed = false; - m_score = score; -} - -inline const std::string &Node::key() const -{ - return m_key; -} - -inline double Node::score() const -{ - return m_score; -} - -inline double Node::scoreForCandidate(const std::string &candidate) const -{ - for (auto unigram : m_unigrams) - { - if (unigram.keyValue.value == candidate) - { - return unigram.score; - } - } - return 0.0; -} - -inline double Node::highestUnigramScore() const -{ - if (m_unigrams.empty()) - { - return 0.0; - } - return m_unigrams[0].score; -} - -inline const KeyValuePair Node::currentKeyValue() const -{ - if (m_selectedUnigramIndex >= m_unigrams.size()) - { - return KeyValuePair(); - } - else - { - return m_candidates[m_selectedUnigramIndex]; - } -} -} // namespace Gramambular - -#endif diff --git a/Source/Modules/LanguageParsers/Gramambular/NodeAnchor.h b/Source/Modules/LanguageParsers/Gramambular/NodeAnchor.h deleted file mode 100644 index 432566a0..00000000 --- a/Source/Modules/LanguageParsers/Gramambular/NodeAnchor.h +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are of: -// (c) 2021 and onwards The vChewing Project (MIT-NTL License). -/* -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -1. The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -2. No trademark license is granted to use the trade names, trademarks, service -marks, or product names of Contributor, except as required to fulfill notice -requirements above. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -#ifndef NODEANCHOR_H_ -#define NODEANCHOR_H_ - -#include - -#include "Node.h" - -namespace Gramambular -{ - -struct NodeAnchor -{ - const Node *node = nullptr; - size_t location = 0; - size_t spanningLength = 0; - double accumulatedScore = 0.0; -}; - -inline std::ostream &operator<<(std::ostream &stream, const NodeAnchor &anchor) -{ - stream << "{@(" << anchor.location << "," << anchor.spanningLength << "),"; - if (anchor.node) - { - stream << *(anchor.node); - } - else - { - stream << "null"; - } - stream << "}"; - return stream; -} - -inline std::ostream &operator<<(std::ostream &stream, const std::vector &anchor) -{ - for (std::vector::const_iterator i = anchor.begin(); i != anchor.end(); ++i) - { - stream << *i; - if (i + 1 != anchor.end()) - { - stream << "<-"; - } - } - - return stream; -} -} // namespace Gramambular - -#endif diff --git a/Source/Modules/LanguageParsers/Gramambular/Span.h b/Source/Modules/LanguageParsers/Gramambular/Span.h deleted file mode 100644 index 57c9a64c..00000000 --- a/Source/Modules/LanguageParsers/Gramambular/Span.h +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are of: -// (c) 2021 and onwards The vChewing Project (MIT-NTL License). -/* -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -1. The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -2. No trademark license is granted to use the trade names, trademarks, service -marks, or product names of Contributor, except as required to fulfill notice -requirements above. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -#ifndef SPAN_H_ -#define SPAN_H_ - -#include -#include -#include - -#include "Node.h" - -namespace Gramambular -{ -class Span -{ - public: - void clear(); - void insertNodeOfLength(const Node &node, size_t length); - void removeNodeOfLengthGreaterThan(size_t length); - - Node *nodeOfLength(size_t length); - size_t maximumLength() const; - - protected: - std::map m_lengthNodeMap; - size_t m_maximumLength = 0; -}; - -inline void Span::clear() -{ - m_lengthNodeMap.clear(); - m_maximumLength = 0; -} - -inline void Span::insertNodeOfLength(const Node &node, size_t length) -{ - m_lengthNodeMap[length] = node; - if (length > m_maximumLength) - { - m_maximumLength = length; - } -} - -inline void Span::removeNodeOfLengthGreaterThan(size_t length) -{ - if (length > m_maximumLength) - { - return; - } - - size_t max = 0; - std::set removeSet; - for (std::map::iterator i = m_lengthNodeMap.begin(), e = m_lengthNodeMap.end(); i != e; ++i) - { - if ((*i).first > length) - { - removeSet.insert((*i).first); - } - else - { - if ((*i).first > max) - { - max = (*i).first; - } - } - } - - for (std::set::iterator i = removeSet.begin(), e = removeSet.end(); i != e; ++i) - { - m_lengthNodeMap.erase(*i); - } - - m_maximumLength = max; -} - -inline Node *Span::nodeOfLength(size_t length) -{ - std::map::iterator f = m_lengthNodeMap.find(length); - return f == m_lengthNodeMap.end() ? 0 : &(*f).second; -} - -inline size_t Span::maximumLength() const -{ - return m_maximumLength; -} -} // namespace Gramambular - -#endif diff --git a/Source/Modules/LanguageParsers/Gramambular/Unigram.h b/Source/Modules/LanguageParsers/Gramambular/Unigram.h deleted file mode 100644 index 7faac48d..00000000 --- a/Source/Modules/LanguageParsers/Gramambular/Unigram.h +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are of: -// (c) 2021 and onwards The vChewing Project (MIT-NTL License). -/* -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -1. The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -2. No trademark license is granted to use the trade names, trademarks, service -marks, or product names of Contributor, except as required to fulfill notice -requirements above. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -#ifndef UNIGRAM_H_ -#define UNIGRAM_H_ - -#include - -#include "KeyValuePair.h" - -namespace Gramambular -{ - -class Unigram -{ - public: - Unigram(); - - KeyValuePair keyValue; - double score; - - bool operator==(const Unigram &another) const; - bool operator<(const Unigram &another) const; - - static bool ScoreCompare(const Unigram &a, const Unigram &b); -}; - -inline std::ostream &operator<<(std::ostream &stream, const Unigram &gram) -{ - std::streamsize p = stream.precision(); - stream.precision(6); - stream << "(" << gram.keyValue << "," << gram.score << ")"; - stream.precision(p); - return stream; -} - -inline std::ostream &operator<<(std::ostream &stream, const std::vector &grams) -{ - stream << "[" << grams.size() << "]=>{"; - - size_t index = 0; - - for (std::vector::const_iterator gi = grams.begin(); gi != grams.end(); ++gi, ++index) - { - stream << index << "=>"; - stream << *gi; - if (gi + 1 != grams.end()) - { - stream << ","; - } - } - - stream << "}"; - return stream; -} - -inline Unigram::Unigram() : score(0.0) -{ -} - -inline bool Unigram::operator==(const Unigram &another) const -{ - return keyValue == another.keyValue && score == another.score; -} - -inline bool Unigram::operator<(const Unigram &another) const -{ - if (keyValue < another.keyValue) - { - return true; - } - else if (keyValue == another.keyValue) - { - return score < another.score; - } - return false; -} - -inline bool Unigram::ScoreCompare(const Unigram &a, const Unigram &b) -{ - return a.score > b.score; -} -} // namespace Gramambular - -#endif diff --git a/Source/Modules/LanguageParsers/Gramambular/Walker.h b/Source/Modules/LanguageParsers/Gramambular/Walker.h deleted file mode 100644 index c5ef2e3d..00000000 --- a/Source/Modules/LanguageParsers/Gramambular/Walker.h +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are of: -// (c) 2021 and onwards The vChewing Project (MIT-NTL License). -/* -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -1. The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -2. No trademark license is granted to use the trade names, trademarks, service -marks, or product names of Contributor, except as required to fulfill notice -requirements above. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -#ifndef WALKER_H_ -#define WALKER_H_ - -#include -#include - -#include "Grid.h" - -namespace Gramambular -{ - -class Walker -{ - public: - explicit Walker(Grid *inGrid); - const std::vector reverseWalk(size_t location, double accumulatedScore = 0.0); - - protected: - Grid *m_grid; -}; - -inline Walker::Walker(Grid *inGrid) : m_grid(inGrid) -{ -} - -inline const std::vector Walker::reverseWalk(size_t location, double accumulatedScore) -{ - if (!location || location > m_grid->width()) - { - return std::vector(); - } - - std::vector> paths; - - std::vector nodes = m_grid->nodesEndingAt(location); - - for (std::vector::iterator ni = nodes.begin(); ni != nodes.end(); ++ni) - { - if (!(*ni).node) - { - continue; - } - - (*ni).accumulatedScore = accumulatedScore + (*ni).node->score(); - - std::vector path = reverseWalk(location - (*ni).spanningLength, (*ni).accumulatedScore); - path.insert(path.begin(), *ni); - - paths.push_back(path); - } - - if (!paths.size()) - { - return std::vector(); - } - - std::vector *result = &*(paths.begin()); - for (std::vector>::iterator pi = paths.begin(); pi != paths.end(); ++pi) - { - if ((*pi).back().accumulatedScore > result->back().accumulatedScore) - { - result = &*pi; - } - } - - return *result; -} -} // namespace Gramambular - -#endif diff --git a/Source/Modules/LangModelRelated/SubLanguageModels/InstantiatedModels/SymbolLM.h b/Source/Modules/LanguageParsers/Megrez/0_Megrez.swift similarity index 67% rename from Source/Modules/LangModelRelated/SubLanguageModels/InstantiatedModels/SymbolLM.h rename to Source/Modules/LanguageParsers/Megrez/0_Megrez.swift index 7c385307..cc4b4804 100644 --- a/Source/Modules/LangModelRelated/SubLanguageModels/InstantiatedModels/SymbolLM.h +++ b/Source/Modules/LanguageParsers/Megrez/0_Megrez.swift @@ -1,6 +1,5 @@ -// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are of: -// (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// Swiftified by (c) 2022 and onwards The vChewing Project (MIT-NTL License). +// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular" (MIT License). /* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -24,31 +23,5 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#ifndef SYMBOLLM_H -#define SYMBOLLM_H - -#include "LanguageModel.h" -#include "UserPhrasesLM.h" -#include -#include -#include - -namespace vChewing -{ - -class SymbolLM : public UserPhrasesLM -{ - public: - bool allowConsolidation() override - { - return false; - } - float overridedValue() override - { - return -13.0; - } -}; - -} // namespace vChewing - -#endif +/// The namespace for this package. +public enum Megrez {} diff --git a/Source/Modules/LanguageParsers/Megrez/1_BlockReadingBuilder.swift b/Source/Modules/LanguageParsers/Megrez/1_BlockReadingBuilder.swift new file mode 100644 index 00000000..652ddc4c --- /dev/null +++ b/Source/Modules/LanguageParsers/Megrez/1_BlockReadingBuilder.swift @@ -0,0 +1,146 @@ +// Swiftified by (c) 2022 and onwards The vChewing Project (MIT-NTL License). +// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular" (MIT License). +/* +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +extension Megrez { + public class BlockReadingBuilder { + let kMaximumBuildSpanLength = 10 // 規定最多可以組成的詞的字數上限為 10 + var mutCursorIndex: Int = 0 + var mutReadings: [String] = [] + var mutGrid: Grid = .init() + var mutLM: LanguageModel + var mutJoinSeparator: String = "" + + public init(lm: LanguageModel) { + mutLM = lm + } + + public func clear() { + mutCursorIndex = 0 + mutReadings.removeAll() + mutGrid.clear() + } + + public func length() -> Int { mutReadings.count } + + public func cursorIndex() -> Int { mutCursorIndex } + + public func setCursorIndex(newIndex: Int) { + mutCursorIndex = min(newIndex, mutReadings.count) + } + + public func insertReadingAtCursor(reading: String) { + mutReadings.insert(reading, at: mutCursorIndex) + mutGrid.expandGridByOneAt(location: mutCursorIndex) + build() + mutCursorIndex += 1 + } + + public func readings() -> [String] { mutReadings } + + @discardableResult public func deleteReadingBeforeCursor() -> Bool { + if mutCursorIndex == 0 { + return false + } + + mutReadings.remove(at: mutCursorIndex - 1) + mutCursorIndex -= 1 + mutGrid.shrinkGridByOneAt(location: mutCursorIndex) + build() + return true + } + + @discardableResult public func deleteReadingAfterCursor() -> Bool { + if mutCursorIndex == mutReadings.count { + return false + } + + mutReadings.remove(at: mutCursorIndex) + mutGrid.shrinkGridByOneAt(location: mutCursorIndex) + build() + return true + } + + @discardableResult public func removeHeadReadings(count: Int) -> Bool { + if count > length() { + return false + } + + var i = 0 + while i < count { + if mutCursorIndex != 0 { + mutCursorIndex -= 1 + } + mutReadings.removeFirst() + mutGrid.shrinkGridByOneAt(location: 0) + build() + i += 1 + } + + return true + } + + public func setJoinSeparator(separator: String) { + mutJoinSeparator = separator + } + + public func joinSeparator() -> String { mutJoinSeparator } + + public func grid() -> Grid { mutGrid } + + public func build() { + // if (mutLM == nil) { return } // 這個出不了 nil,所以註釋掉。 + + let itrBegin: Int = + (mutCursorIndex < kMaximumBuildSpanLength) ? 0 : mutCursorIndex - kMaximumBuildSpanLength + let itrEnd: Int = min(mutCursorIndex + kMaximumBuildSpanLength, mutReadings.count) + + var p = itrBegin + while p < itrEnd { + var q = 1 + while q <= kMaximumBuildSpanLength, p + q <= itrEnd { + let strSlice = mutReadings[p..<(p + q)] + let combinedReading: String = join(slice: strSlice, separator: mutJoinSeparator) + if !mutGrid.hasMatchedNode(location: p, spanningLength: q, key: combinedReading) { + let unigrams: [Unigram] = mutLM.unigramsFor(key: combinedReading) + if !unigrams.isEmpty { + let n = Node(key: combinedReading, unigrams: unigrams) + mutGrid.insertNode(node: n, location: p, spanningLength: q) + } + } + q += 1 + } + p += 1 + } + } + + public func join(slice strSlice: ArraySlice, separator: String) -> String { + var arrResult: [String] = [] + for value in strSlice { + arrResult.append(value) + } + return arrResult.joined(separator: separator) + } + } +} diff --git a/Source/Modules/LanguageParsers/Megrez/1_Walker.swift b/Source/Modules/LanguageParsers/Megrez/1_Walker.swift new file mode 100644 index 00000000..7c2a5051 --- /dev/null +++ b/Source/Modules/LanguageParsers/Megrez/1_Walker.swift @@ -0,0 +1,74 @@ +// Swiftified by (c) 2022 and onwards The vChewing Project (MIT-NTL License). +// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular" (MIT License). +/* +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +extension Megrez { + public class Walker { + var mutGrid: Grid + + public init(grid: Megrez.Grid = Megrez.Grid()) { + mutGrid = grid + } + + public func reverseWalk(at location: Int, score accumulatedScore: Double = 0.0) -> [NodeAnchor] { + if location == 0 || location > mutGrid.width() { + return [] as [NodeAnchor] + } + + var paths: [[NodeAnchor]] = [] + let nodes: [NodeAnchor] = mutGrid.nodesEndingAt(location: location) + + for n in nodes { + var n = n + if n.node == nil { + continue + } + + n.accumulatedScore = accumulatedScore + n.node!.score() + + var path: [NodeAnchor] = reverseWalk( + at: location - n.spanningLength, + score: n.accumulatedScore + ) + path.insert(n, at: 0) + + paths.append(path) + } + + if !paths.isEmpty { + if var result = paths.first { + for value in paths { + if let vLast = value.last, let rLast = result.last { + if vLast.accumulatedScore > rLast.accumulatedScore { + result = value + } + } + } + return result + } + } + return [] as [NodeAnchor] + } + } +} diff --git a/Source/Modules/LanguageParsers/Megrez/2_Grid.swift b/Source/Modules/LanguageParsers/Megrez/2_Grid.swift new file mode 100644 index 00000000..c391b425 --- /dev/null +++ b/Source/Modules/LanguageParsers/Megrez/2_Grid.swift @@ -0,0 +1,180 @@ +// Swiftified by (c) 2022 and onwards The vChewing Project (MIT-NTL License). +// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular" (MIT License). +/* +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +extension Megrez { + public class Grid { + var mutSpans: [Megrez.Span] + + public init() { + mutSpans = [Megrez.Span]() + } + + public func clear() { + mutSpans = [Megrez.Span]() + } + + public func insertNode(node: Node, location: Int, spanningLength: Int) { + if location >= mutSpans.count { + let diff = location - mutSpans.count + 1 + var i = 0 + while i < diff { + mutSpans.append(Span()) + i += 1 + } + } + mutSpans[location].insert(node: node, length: spanningLength) + } + + public func hasMatchedNode(location: Int, spanningLength: Int, key: String) -> Bool { + if location > mutSpans.count { + return false + } + + let n = mutSpans[location].node(length: spanningLength) + return n == nil ? false : key == n?.key() + } + + public func expandGridByOneAt(location: Int) { + mutSpans.append(Span()) + if location > 0, location < mutSpans.count { + var i = 0 + while i < location { + // zaps overlapping spans + mutSpans[i].removeNodeOfLengthGreaterThan(location - i) + i += 1 + } + } + } + + public func shrinkGridByOneAt(location: Int) { + if location >= mutSpans.count { + return + } + + mutSpans.remove(at: location) + var i = 0 + while i < location { + // zaps overlapping spans + mutSpans[i].removeNodeOfLengthGreaterThan(location - i) + i += 1 + } + } + + public func width() -> Int { mutSpans.count } + + public func nodesEndingAt(location: Int) -> [NodeAnchor] { + var results: [NodeAnchor] = [] + if !mutSpans.isEmpty, location <= mutSpans.count { + var i = 0 + while i < location { + let span = mutSpans[i] + if i + span.maximumLength >= location { + if let np = span.node(length: location - i) { + results.append( + NodeAnchor( + node: np, + location: i, + spanningLength: location - i + ) + ) + } + } + i += 1 + } + } + return results + } + + public func nodesCrossingOrEndingAt(location: Int) -> [NodeAnchor] { + var results: [NodeAnchor] = [] + if !mutSpans.isEmpty, location <= mutSpans.count { + var i = 0 + while i < location { + let span = mutSpans[i] + if i + span.maximumLength >= location { + var j = 1 + while j <= span.maximumLength { + if i + j < location { + j += 1 + continue + } + if let np = span.node(length: j) { + results.append( + NodeAnchor( + node: np, + location: i, + spanningLength: location - i + ) + ) + } + j += 1 + } + } + i += 1 + } + } + return results + } + + public func fixNodeSelectedCandidate(location: Int, value: String) -> NodeAnchor { + var node = NodeAnchor() + let nodes = nodesCrossingOrEndingAt(location: location) + for nodeAnchor in nodes { + // Reset the candidate-fixed state of every node at the location. + let candidates = nodeAnchor.node?.candidates() ?? [] + nodeAnchor.node?.resetCandidate() + + for (i, candidate) in candidates.enumerated() { + if candidate.value == value { + nodeAnchor.node?.selectCandidateAt(index: i) + node = nodeAnchor + break + } + } + } + return node + } + + public func overrideNodeScoreForSelectedCandidate(location: Int, value: String, overridingScore: Double) { + for nodeAnchor in nodesCrossingOrEndingAt(location: location) { + var nodeAnchor = nodeAnchor + if let theNode = nodeAnchor.node { + let candidates = theNode.candidates() + // Reset the candidate-fixed state of every node at the location. + theNode.resetCandidate() + nodeAnchor.node = theNode + + for (i, candidate) in candidates.enumerated() { + if candidate.value == value { + theNode.selectFloatingCandidateAt(index: i, score: overridingScore) + nodeAnchor.node = theNode + break + } + } + } + } + } + } +} diff --git a/Source/Modules/LangModelRelated/SubLanguageModels/InstantiatedModels/CNSLM.h b/Source/Modules/LanguageParsers/Megrez/3_NodeAnchor.swift similarity index 67% rename from Source/Modules/LangModelRelated/SubLanguageModels/InstantiatedModels/CNSLM.h rename to Source/Modules/LanguageParsers/Megrez/3_NodeAnchor.swift index f464255f..0a130a58 100644 --- a/Source/Modules/LangModelRelated/SubLanguageModels/InstantiatedModels/CNSLM.h +++ b/Source/Modules/LanguageParsers/Megrez/3_NodeAnchor.swift @@ -1,6 +1,5 @@ -// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are of: -// (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// Swiftified by (c) 2022 and onwards The vChewing Project (MIT-NTL License). +// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular" (MIT License). /* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -24,31 +23,14 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#ifndef CNSLM_H -#define CNSLM_H - -#include "LanguageModel.h" -#include "UserPhrasesLM.h" -#include -#include -#include - -namespace vChewing -{ - -class CNSLM : public UserPhrasesLM -{ - public: - bool allowConsolidation() override - { - return false; +extension Megrez { + @frozen public struct NodeAnchor { + public var node: Node? + public var location: Int = 0 + public var spanningLength: Int = 0 + public var accumulatedScore: Double = 0.0 + public var keyLength: Int { + node?.key().count ?? 0 } - float overridedValue() override - { - return -11.0; - } -}; - -} // namespace vChewing - -#endif + } +} diff --git a/Source/Modules/LanguageParsers/Megrez/3_Span.swift b/Source/Modules/LanguageParsers/Megrez/3_Span.swift new file mode 100644 index 00000000..5b5eee7a --- /dev/null +++ b/Source/Modules/LanguageParsers/Megrez/3_Span.swift @@ -0,0 +1,74 @@ +// Swiftified by (c) 2022 and onwards The vChewing Project (MIT-NTL License). +// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular" (MIT License). +/* +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +extension Megrez { + @frozen public struct Span { + private var mutLengthNodeMap: [Int: Megrez.Node] + private var mutMaximumLength: Int + var maximumLength: Int { + mutMaximumLength + } + + public init() { + mutLengthNodeMap = [:] + mutMaximumLength = 0 + } + + mutating func clear() { + mutLengthNodeMap.removeAll() + mutMaximumLength = 0 + } + + mutating func insert(node: Node, length: Int) { + mutLengthNodeMap[length] = node + if length > mutMaximumLength { + mutMaximumLength = length + } + } + + mutating func removeNodeOfLengthGreaterThan(_ length: Int) { + if length > mutMaximumLength { return } + var max = 0 + var removalList: [Int: Megrez.Node] = [:] + for key in mutLengthNodeMap.keys { + if key > length { + removalList[key] = mutLengthNodeMap[key] + } else { + if key > max { + max = key + } + } + } + for key in removalList.keys { + mutLengthNodeMap.removeValue(forKey: key) + } + mutMaximumLength = max + } + + public func node(length: Int) -> Node? { + mutLengthNodeMap[length] + } + } +} diff --git a/Source/Modules/LanguageParsers/Megrez/4_Node.swift b/Source/Modules/LanguageParsers/Megrez/4_Node.swift new file mode 100644 index 00000000..fdf0838d --- /dev/null +++ b/Source/Modules/LanguageParsers/Megrez/4_Node.swift @@ -0,0 +1,161 @@ +// Swiftified by (c) 2022 and onwards The vChewing Project (MIT-NTL License). +// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular" (MIT License). +/* +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +extension Megrez { + public class Node { + let mutLM: LanguageModel + var mutKey: String + var mutScore: Double = 0 + var mutUnigrams: [Unigram] + var mutCandidates: [KeyValuePair] + var mutValueUnigramIndexMap: [String: Int] + var mutPrecedingBigramMap: [KeyValuePair: [Megrez.Bigram]] + + var mutCandidateFixed: Bool = false + var mutSelectedUnigramIndex: Int = 0 + + public init(key: String, unigrams: [Megrez.Unigram], bigrams: [Megrez.Bigram] = []) { + mutLM = LanguageModel() + + mutKey = key + mutScore = 0 + + mutUnigrams = unigrams + mutCandidates = [] + mutValueUnigramIndexMap = [:] + mutPrecedingBigramMap = [:] + + mutCandidateFixed = false + mutSelectedUnigramIndex = 0 + + if bigrams == [] { + node(key: key, unigrams: unigrams, bigrams: bigrams) + } else { + node(key: key, unigrams: unigrams) + } + } + + public func node(key: String, unigrams: [Megrez.Unigram], bigrams: [Megrez.Bigram] = []) { + var unigrams = unigrams + mutKey = key + unigrams.sort { + $0.score > $1.score + } + + if !mutUnigrams.isEmpty { + mutScore = mutUnigrams[0].score + } + + for (i, theGram) in unigrams.enumerated() { + mutValueUnigramIndexMap[theGram.keyValue.value] = i + mutCandidates.append(theGram.keyValue) + } + + for gram in bigrams { + mutPrecedingBigramMap[gram.precedingKeyValue]?.append(gram) + } + } + + public func primeNodeWith(precedingKeyValues: [KeyValuePair]) { + var newIndex = mutSelectedUnigramIndex + var max = mutScore + + if !isCandidateFixed() { + for neta in precedingKeyValues { + let bigrams = mutPrecedingBigramMap[neta] ?? [] + for bigram in bigrams { + if bigram.score > max { + if let valRetrieved = mutValueUnigramIndexMap[bigram.keyValue.value] { + newIndex = valRetrieved as Int + max = bigram.score + } + } + } + } + } + + if mutScore != max { + mutScore = max + } + + if mutSelectedUnigramIndex != newIndex { + mutSelectedUnigramIndex = newIndex + } + } + + public func isCandidateFixed() -> Bool { mutCandidateFixed } + + public func candidates() -> [KeyValuePair] { mutCandidates } + + public func selectCandidateAt(index: Int = 0, fix: Bool = false) { + mutSelectedUnigramIndex = index >= mutUnigrams.count ? 0 : index + mutCandidateFixed = fix + mutScore = 99 + } + + public func resetCandidate() { + mutSelectedUnigramIndex = 0 + mutCandidateFixed = false + if !mutUnigrams.isEmpty { + mutScore = mutUnigrams[0].score + } + } + + public func selectFloatingCandidateAt(index: Int, score: Double) { + mutSelectedUnigramIndex = index >= mutUnigrams.count ? 0 : index + mutCandidateFixed = false + mutScore = score + } + + public func key() -> String { mutKey } + + public func score() -> Double { mutScore } + + public func scoreFor(candidate: String) -> Double { + for unigram in mutUnigrams { + if unigram.keyValue.value == candidate { + return unigram.score + } + } + return 0.0 + } + + public func currentKeyValue() -> KeyValuePair { + mutSelectedUnigramIndex >= mutUnigrams.count ? KeyValuePair() : mutCandidates[mutSelectedUnigramIndex] + } + + public func highestUnigramScore() -> Double { + mutUnigrams.isEmpty ? 0.0 : mutUnigrams[0].score + } + + public static func == (lhs: Node, rhs: Node) -> Bool { + lhs.mutUnigrams == rhs.mutUnigrams && lhs.mutCandidates == rhs.mutCandidates + && lhs.mutValueUnigramIndexMap == rhs.mutValueUnigramIndexMap + && lhs.mutPrecedingBigramMap == rhs.mutPrecedingBigramMap + && lhs.mutCandidateFixed == rhs.mutCandidateFixed + && lhs.mutSelectedUnigramIndex == rhs.mutSelectedUnigramIndex + } + } +} diff --git a/Source/Modules/LanguageParsers/Gramambular/LanguageModel.h b/Source/Modules/LanguageParsers/Megrez/5_LanguageModel.swift similarity index 60% rename from Source/Modules/LanguageParsers/Gramambular/LanguageModel.h rename to Source/Modules/LanguageParsers/Megrez/5_LanguageModel.swift index 1049c011..383cdbc9 100644 --- a/Source/Modules/LanguageParsers/Gramambular/LanguageModel.h +++ b/Source/Modules/LanguageParsers/Megrez/5_LanguageModel.swift @@ -1,6 +1,5 @@ -// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are of: -// (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// Swiftified by (c) 2022 and onwards The vChewing Project (MIT-NTL License). +// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular" (MIT License). /* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -24,29 +23,22 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#ifndef LANGUAGEMODEL_H_ -#define LANGUAGEMODEL_H_ +extension Megrez { + // 這裡充其量只是框架,回頭實際使用時需要派生一個型別、且重寫相關函數。 + // 這裡寫了一點假內容,不然有些 Swift 格式化工具會破壞掉函數的參數設計。 + open class LanguageModel { + public init() {} -#include -#include - -#include "Bigram.h" -#include "Unigram.h" - -namespace Gramambular -{ - -class LanguageModel -{ - public: - virtual ~LanguageModel() - { + open func unigramsFor(key: String) -> [Megrez.Unigram] { + key.isEmpty ? [Megrez.Unigram]() : [Megrez.Unigram]() } - virtual const std::vector bigramsForKeys(const std::string &preceedingKey, const std::string &key) = 0; - virtual const std::vector unigramsForKey(const std::string &key) = 0; - virtual bool hasUnigramsForKey(const std::string &key) = 0; -}; -} // namespace Gramambular + open func bigramsForKeys(precedingKey: String, key: String) -> [Megrez.Bigram] { + precedingKey == key ? [Megrez.Bigram]() : [Megrez.Bigram]() + } -#endif + open func hasUnigramsFor(key: String) -> Bool { + key.count != 0 + } + } +} diff --git a/Source/Modules/LanguageParsers/Megrez/6_Bigram.swift b/Source/Modules/LanguageParsers/Megrez/6_Bigram.swift new file mode 100644 index 00000000..f934f1a9 --- /dev/null +++ b/Source/Modules/LanguageParsers/Megrez/6_Bigram.swift @@ -0,0 +1,74 @@ +// Swiftified by (c) 2022 and onwards The vChewing Project (MIT-NTL License). +// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular" (MIT License). +/* +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +extension Megrez { + @frozen public struct Bigram: Equatable { + public var keyValue: KeyValuePair + public var precedingKeyValue: KeyValuePair + public var score: Double + // var paired: String + + public init(precedingKeyValue: KeyValuePair, keyValue: KeyValuePair, score: Double) { + self.keyValue = keyValue + self.precedingKeyValue = precedingKeyValue + self.score = score + // paired = "(" + keyValue.paired + "|" + precedingKeyValue.paired + "," + String(score) + ")" + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(keyValue) + hasher.combine(precedingKeyValue) + hasher.combine(score) + // hasher.combine(paired) + } + + // static func getPairedBigrams(grams: [Bigram]) -> String { + // var arrOutputContent = [""] + // var index = 0 + // for gram in grams { + // arrOutputContent.append(contentsOf: [String(index) + "=>" + gram.paired]) + // index += 1 + // } + // return "[" + String(grams.count) + "]=>{" + arrOutputContent.joined(separator: ",") + "}" + // } + + public static func == (lhs: Bigram, rhs: Bigram) -> Bool { + lhs.precedingKeyValue == rhs.precedingKeyValue && lhs.keyValue == rhs.keyValue && lhs.score == rhs.score + } + + public static func < (lhs: Bigram, rhs: Bigram) -> Bool { + lhs.precedingKeyValue < rhs.precedingKeyValue + || (lhs.keyValue < rhs.keyValue || (lhs.keyValue == rhs.keyValue && lhs.keyValue < rhs.keyValue)) + } + + var description: String { + "\(keyValue):\(score)" + } + + var debugDescription: String { + "Bigram(keyValue: \(keyValue), score: \(score))" + } + } +} diff --git a/Source/Modules/LanguageParsers/Megrez/6_Unigram.swift b/Source/Modules/LanguageParsers/Megrez/6_Unigram.swift new file mode 100644 index 00000000..793b5db6 --- /dev/null +++ b/Source/Modules/LanguageParsers/Megrez/6_Unigram.swift @@ -0,0 +1,75 @@ +// Swiftified by (c) 2022 and onwards The vChewing Project (MIT-NTL License). +// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular" (MIT License). +/* +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +extension Megrez { + @frozen public struct Unigram: Equatable { + public var keyValue: KeyValuePair + public var score: Double + // var paired: String + + public init(keyValue: KeyValuePair, score: Double) { + self.keyValue = keyValue + self.score = score + // paired = "(" + keyValue.paired + "," + String(score) + ")" + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(keyValue) + hasher.combine(score) + // hasher.combine(paired) + } + + // 這個函數不再需要了。 + public static func compareScore(a: Unigram, b: Unigram) -> Bool { + a.score > b.score + } + + // static func getPairedUnigrams(grams: [Unigram]) -> String { + // var arrOutputContent = [""] + // var index = 0 + // for gram in grams { + // arrOutputContent.append(contentsOf: [String(index) + "=>" + gram.paired]) + // index += 1 + // } + // return "[" + String(grams.count) + "]=>{" + arrOutputContent.joined(separator: ",") + "}" + // } + + public static func == (lhs: Unigram, rhs: Unigram) -> Bool { + lhs.keyValue == rhs.keyValue && lhs.score == rhs.score + } + + public static func < (lhs: Unigram, rhs: Unigram) -> Bool { + lhs.keyValue < rhs.keyValue || (lhs.keyValue == rhs.keyValue && lhs.keyValue < rhs.keyValue) + } + + var description: String { + "\(keyValue):\(score)" + } + + var debugDescription: String { + "Unigram(keyValue: \(keyValue), score: \(score))" + } + } +} diff --git a/Source/Modules/LanguageParsers/Megrez/7_KeyValuePair.swift b/Source/Modules/LanguageParsers/Megrez/7_KeyValuePair.swift new file mode 100644 index 00000000..b10a9e83 --- /dev/null +++ b/Source/Modules/LanguageParsers/Megrez/7_KeyValuePair.swift @@ -0,0 +1,72 @@ +// Swiftified by (c) 2022 and onwards The vChewing Project (MIT-NTL License). +// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular" (MIT License). +/* +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +extension Megrez { + @frozen public struct KeyValuePair: Equatable, Hashable, Comparable { + public var key: String + public var value: String + // public var paired: String + + public init(key: String = "", value: String = "") { + self.key = key + self.value = value + // paired = "(" + key + "," + value + ")" + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(key) + hasher.combine(value) + // hasher.combine(paired) + } + + public static func == (lhs: KeyValuePair, rhs: KeyValuePair) -> Bool { + lhs.key.count == rhs.key.count && lhs.value == rhs.value + } + + public static func < (lhs: KeyValuePair, rhs: KeyValuePair) -> Bool { + (lhs.key.count < rhs.key.count) || (lhs.key.count == rhs.key.count && lhs.value < rhs.value) + } + + public static func > (lhs: KeyValuePair, rhs: KeyValuePair) -> Bool { + (lhs.key.count > rhs.key.count) || (lhs.key.count == rhs.key.count && lhs.value > rhs.value) + } + + public static func <= (lhs: KeyValuePair, rhs: KeyValuePair) -> Bool { + (lhs.key.count <= rhs.key.count) || (lhs.key.count == rhs.key.count && lhs.value <= rhs.value) + } + + public static func >= (lhs: KeyValuePair, rhs: KeyValuePair) -> Bool { + (lhs.key.count >= rhs.key.count) || (lhs.key.count == rhs.key.count && lhs.value >= rhs.value) + } + + public var description: String { + "(\(key), \(value))" + } + + public var debugDescription: String { + "KeyValuePair(key: \(key), value: \(value))" + } + } +} diff --git a/Source/Modules/SFX/clsSFX.swift b/Source/Modules/SFX/clsSFX.swift index e6d42546..77fee8f3 100644 --- a/Source/Modules/SFX/clsSFX.swift +++ b/Source/Modules/SFX/clsSFX.swift @@ -27,43 +27,43 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import Cocoa public class clsSFX: NSObject, NSSoundDelegate { - private static let shared = clsSFX() - override private init() { - super.init() - } + private static let shared = clsSFX() + override private init() { + super.init() + } - private var currentBeep: NSSound? - private func beep() { - // Stop existing beep - if let beep = currentBeep { - if beep.isPlaying { - beep.stop() - } - } - // Create a new beep sound if possible - var sndBeep: String - if mgrPrefs.shouldNotFartInLieuOfBeep == false { - sndBeep = "Fart" - } else { - sndBeep = "Beep" - } - guard - let beep = NSSound(named: sndBeep) - else { - NSSound.beep() - return - } - beep.delegate = self - beep.volume = 0.4 - beep.play() - currentBeep = beep - } + private var currentBeep: NSSound? + private func beep() { + // Stop existing beep + if let beep = currentBeep { + if beep.isPlaying { + beep.stop() + } + } + // Create a new beep sound if possible + var sndBeep: String + if mgrPrefs.shouldNotFartInLieuOfBeep == false { + sndBeep = "Fart" + } else { + sndBeep = "Beep" + } + guard + let beep = NSSound(named: sndBeep) + else { + NSSound.beep() + return + } + beep.delegate = self + beep.volume = 0.4 + beep.play() + currentBeep = beep + } - @objc public func sound(_: NSSound, didFinishPlaying _: Bool) { - currentBeep = nil - } + public func sound(_: NSSound, didFinishPlaying _: Bool) { + currentBeep = nil + } - @objc static func beep() { - shared.beep() - } + static func beep() { + shared.beep() + } } diff --git a/Source/Modules/main.swift b/Source/Modules/main.swift index 185e29bf..bf266f8f 100644 --- a/Source/Modules/main.swift +++ b/Source/Modules/main.swift @@ -30,32 +30,32 @@ import InputMethodKit let kConnectionName = "vChewing_1_Connection" if CommandLine.arguments.count > 1 { - if CommandLine.arguments[1] == "install" { - let exitCode = IME.registerInputMethod() - exit(exitCode) - } - if CommandLine.arguments[1] == "uninstall" { - let exitCode = IME.uninstall(isSudo: IME.isSudoMode) - exit(exitCode) - } + if CommandLine.arguments[1] == "install" { + let exitCode = IME.registerInputMethod() + exit(exitCode) + } + if CommandLine.arguments[1] == "uninstall" { + let exitCode = IME.uninstall(isSudo: IME.isSudoMode) + exit(exitCode) + } } guard let mainNibName = Bundle.main.infoDictionary?["NSMainNibFile"] as? String else { - NSLog("Fatal error: NSMainNibFile key not defined in Info.plist.") - exit(-1) + NSLog("Fatal error: NSMainNibFile key not defined in Info.plist.") + exit(-1) } let loaded = Bundle.main.loadNibNamed(mainNibName, owner: NSApp, topLevelObjects: nil) if !loaded { - NSLog("Fatal error: Cannot load \(mainNibName).") - exit(-1) + NSLog("Fatal error: Cannot load \(mainNibName).") + exit(-1) } guard let bundleID = Bundle.main.bundleIdentifier, - let server = IMKServer(name: kConnectionName, bundleIdentifier: bundleID) + let server = IMKServer(name: kConnectionName, bundleIdentifier: bundleID) else { - NSLog("Fatal error: Cannot initialize input method server with connection \(kConnectionName).") - exit(-1) + NSLog("Fatal error: Cannot initialize input method server with connection \(kConnectionName).") + exit(-1) } NSApp.run() diff --git a/Source/Resources/Base.lproj/Localizable.strings b/Source/Resources/Base.lproj/Localizable.strings index f7f17571..223c3dfd 100644 --- a/Source/Resources/Base.lproj/Localizable.strings +++ b/Source/Resources/Base.lproj/Localizable.strings @@ -57,6 +57,9 @@ "Edit User Symbol & Emoji Data…" = "Edit User Symbol & Emoji Data…"; "Choose your desired user data folder." = "Choose your desired user data folder."; "Cursor is between \"%@\" and \"%@\"." = "Cursor is between \"%@\" and \"%@\"."; +"Loading CHS Core Dict..." = "Loading CHS Core Dict..."; +"Loading CHT Core Dict..." = "Loading CHT Core Dict..."; +"Core Dict loading complete." = "Core Dict loading complete."; // The followings are the category names used in the Symbol menu. "catCommonSymbols" = "CommonSymbols"; diff --git a/Source/Resources/en.lproj/Localizable.strings b/Source/Resources/en.lproj/Localizable.strings index f7f17571..223c3dfd 100644 --- a/Source/Resources/en.lproj/Localizable.strings +++ b/Source/Resources/en.lproj/Localizable.strings @@ -57,6 +57,9 @@ "Edit User Symbol & Emoji Data…" = "Edit User Symbol & Emoji Data…"; "Choose your desired user data folder." = "Choose your desired user data folder."; "Cursor is between \"%@\" and \"%@\"." = "Cursor is between \"%@\" and \"%@\"."; +"Loading CHS Core Dict..." = "Loading CHS Core Dict..."; +"Loading CHT Core Dict..." = "Loading CHT Core Dict..."; +"Core Dict loading complete." = "Core Dict loading complete."; // The followings are the category names used in the Symbol menu. "catCommonSymbols" = "CommonSymbols"; diff --git a/Source/Resources/ja.lproj/Localizable.strings b/Source/Resources/ja.lproj/Localizable.strings index 1f515508..50be3fbf 100644 --- a/Source/Resources/ja.lproj/Localizable.strings +++ b/Source/Resources/ja.lproj/Localizable.strings @@ -57,6 +57,9 @@ "Edit User Symbol & Emoji Data…" = "ユーザー符号&絵文字辞書を編集…"; "Choose your desired user data folder." = "欲しがるユーザー辞書フォルダをお選びください。"; "Cursor is between \"%@\" and \"%@\"." = "カーソルは「%@」と「%@」に間れ。"; +"Loading CHS Core Dict..." = "簡体中国語核心辞書読込中…"; +"Loading CHT Core Dict..." = "繁体中国語核心辞書読込中…"; +"Core Dict loading complete." = "核心辞書読込完了"; // The followings are the category names used in the Symbol menu. "catCommonSymbols" = "常用"; diff --git a/Source/Resources/zh-Hans.lproj/Localizable.strings b/Source/Resources/zh-Hans.lproj/Localizable.strings index a83c8d68..2d33f0be 100644 --- a/Source/Resources/zh-Hans.lproj/Localizable.strings +++ b/Source/Resources/zh-Hans.lproj/Localizable.strings @@ -57,6 +57,9 @@ "Edit User Symbol & Emoji Data…" = "编辑自订符号&绘文字资料…"; "Choose your desired user data folder." = "请选择您想指定的使用者语汇档案目录。"; "Cursor is between \"%@\" and \"%@\"." = "游标介于「%@」与「%@」之间。"; +"Loading CHS Core Dict..." = "载入简体中文核心辞典…"; +"Loading CHT Core Dict..." = "载入繁体中文核心辞典…"; +"Core Dict loading complete." = "核心辞典载入完毕"; // The followings are the category names used in the Symbol menu. "catCommonSymbols" = "常用"; diff --git a/Source/Resources/zh-Hant.lproj/Localizable.strings b/Source/Resources/zh-Hant.lproj/Localizable.strings index f300f1ee..983f74ac 100644 --- a/Source/Resources/zh-Hant.lproj/Localizable.strings +++ b/Source/Resources/zh-Hant.lproj/Localizable.strings @@ -57,6 +57,9 @@ "Edit User Symbol & Emoji Data…" = "編輯自訂符號&繪文字資料…"; "Choose your desired user data folder." = "請選擇您想指定的使用者語彙檔案目錄。"; "Cursor is between \"%@\" and \"%@\"." = "游標介於「%@」與「%@」之間。"; +"Loading CHS Core Dict..." = "載入簡體中文核心辭典…"; +"Loading CHT Core Dict..." = "載入繁體中文核心辭典…"; +"Core Dict loading complete." = "核心辭典載入完畢"; // The followings are the category names used in the Symbol menu. "catCommonSymbols" = "常用"; diff --git a/Source/UI/CandidateUI/ctlCandidate.swift b/Source/UI/CandidateUI/ctlCandidate.swift index 699c1c78..f4333e9c 100644 --- a/Source/UI/CandidateUI/ctlCandidate.swift +++ b/Source/UI/CandidateUI/ctlCandidate.swift @@ -26,155 +26,151 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import Cocoa -@objc(VTCandidateKeyLabel) public class CandidateKeyLabel: NSObject { - @objc public private(set) var key: String - @objc public private(set) var displayedText: String + public private(set) var key: String + public private(set) var displayedText: String - public init(key: String, displayedText: String) { - self.key = key - self.displayedText = displayedText - super.init() - } + public init(key: String, displayedText: String) { + self.key = key + self.displayedText = displayedText + super.init() + } } -@objc(ctlCandidateDelegate) public protocol ctlCandidateDelegate: AnyObject { - func candidateCountForController(_ controller: ctlCandidate) -> UInt - func ctlCandidate(_ controller: ctlCandidate, candidateAtIndex index: UInt) - -> String - func ctlCandidate( - _ controller: ctlCandidate, didSelectCandidateAtIndex index: UInt - ) + func candidateCountForController(_ controller: ctlCandidate) -> UInt + func ctlCandidate(_ controller: ctlCandidate, candidateAtIndex index: UInt) + -> String + func ctlCandidate( + _ controller: ctlCandidate, didSelectCandidateAtIndex index: UInt + ) } -@objc(ctlCandidate) public class ctlCandidate: NSWindowController { - @objc public weak var delegate: ctlCandidateDelegate? { - didSet { - reloadData() - } - } + public weak var delegate: ctlCandidateDelegate? { + didSet { + reloadData() + } + } - @objc public var selectedCandidateIndex: UInt = .max - @objc public var visible: Bool = false { - didSet { - NSObject.cancelPreviousPerformRequests(withTarget: self) - if visible { - window?.perform(#selector(NSWindow.orderFront(_:)), with: self, afterDelay: 0.0) - } else { - window?.perform(#selector(NSWindow.orderOut(_:)), with: self, afterDelay: 0.0) - } - } - } + public var selectedCandidateIndex: UInt = .max + public var visible: Bool = false { + didSet { + NSObject.cancelPreviousPerformRequests(withTarget: self) + if visible { + window?.perform(#selector(NSWindow.orderFront(_:)), with: self, afterDelay: 0.0) + } else { + window?.perform(#selector(NSWindow.orderOut(_:)), with: self, afterDelay: 0.0) + } + } + } - @objc public var windowTopLeftPoint: NSPoint { - get { - guard let frameRect = window?.frame else { - return NSPoint.zero - } - return NSPoint(x: frameRect.minX, y: frameRect.maxY) - } - set { - DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) { - self.set(windowTopLeftPoint: newValue, bottomOutOfScreenAdjustmentHeight: 0) - } - } - } + public var windowTopLeftPoint: NSPoint { + get { + guard let frameRect = window?.frame else { + return NSPoint.zero + } + return NSPoint(x: frameRect.minX, y: frameRect.maxY) + } + set { + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) { + self.set(windowTopLeftPoint: newValue, bottomOutOfScreenAdjustmentHeight: 0) + } + } + } - @objc public var keyLabels: [CandidateKeyLabel] = ["1", "2", "3", "4", "5", "6", "7", "8", "9"] - .map { - CandidateKeyLabel(key: $0, displayedText: $0) - } + public var keyLabels: [CandidateKeyLabel] = ["1", "2", "3", "4", "5", "6", "7", "8", "9"] + .map { + CandidateKeyLabel(key: $0, displayedText: $0) + } - @objc public var keyLabelFont: NSFont = NSFont.monospacedDigitSystemFont( - ofSize: 14, weight: .medium - ) - @objc public var candidateFont: NSFont = NSFont.systemFont(ofSize: 18) - @objc public var tooltip: String = "" + public var keyLabelFont: NSFont = NSFont.monospacedDigitSystemFont( + ofSize: 14, weight: .medium + ) + public var candidateFont: NSFont = NSFont.systemFont(ofSize: 18) + public var tooltip: String = "" - @objc public func reloadData() {} + public func reloadData() {} - @objc public func showNextPage() -> Bool { - false - } + public func showNextPage() -> Bool { + false + } - @objc public func showPreviousPage() -> Bool { - false - } + public func showPreviousPage() -> Bool { + false + } - @objc public func highlightNextCandidate() -> Bool { - false - } + public func highlightNextCandidate() -> Bool { + false + } - @objc public func highlightPreviousCandidate() -> Bool { - false - } + public func highlightPreviousCandidate() -> Bool { + false + } - @objc public func candidateIndexAtKeyLabelIndex(_: UInt) -> UInt { - UInt.max - } + public func candidateIndexAtKeyLabelIndex(_: UInt) -> UInt { + UInt.max + } - /// Sets the location of the candidate window. - /// - /// Please note that the method has side effects that modifies - /// `windowTopLeftPoint` to make the candidate window to stay in at least - /// in a screen. - /// - /// - Parameters: - /// - windowTopLeftPoint: The given location. - /// - height: The height that helps the window not to be out of the bottom - /// of a screen. - @objc(setWindowTopLeftPoint:bottomOutOfScreenAdjustmentHeight:) - public func set(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight height: CGFloat) { - DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) { - self.doSet( - windowTopLeftPoint: windowTopLeftPoint, bottomOutOfScreenAdjustmentHeight: height - ) - } - } + /// Sets the location of the candidate window. + /// + /// Please note that the method has side effects that modifies + /// `windowTopLeftPoint` to make the candidate window to stay in at least + /// in a screen. + /// + /// - Parameters: + /// - windowTopLeftPoint: The given location. + /// - height: The height that helps the window not to be out of the bottom + /// of a screen. + public func set(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight height: CGFloat) { + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) { + self.doSet( + windowTopLeftPoint: windowTopLeftPoint, bottomOutOfScreenAdjustmentHeight: height + ) + } + } - func doSet(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight height: CGFloat) { - var adjustedPoint = windowTopLeftPoint - var adjustedHeight = height + func doSet(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight height: CGFloat) { + var adjustedPoint = windowTopLeftPoint + var adjustedHeight = height - var screenFrame = NSScreen.main?.visibleFrame ?? NSRect.zero - for screen in NSScreen.screens { - let frame = screen.visibleFrame - if windowTopLeftPoint.x >= frame.minX, windowTopLeftPoint.x <= frame.maxX, - windowTopLeftPoint.y >= frame.minY, windowTopLeftPoint.y <= frame.maxY - { - screenFrame = frame - break - } - } + var screenFrame = NSScreen.main?.visibleFrame ?? NSRect.zero + for screen in NSScreen.screens { + let frame = screen.visibleFrame + if windowTopLeftPoint.x >= frame.minX, windowTopLeftPoint.x <= frame.maxX, + windowTopLeftPoint.y >= frame.minY, windowTopLeftPoint.y <= frame.maxY + { + screenFrame = frame + break + } + } - if adjustedHeight > screenFrame.size.height / 2.0 { - adjustedHeight = 0.0 - } + if adjustedHeight > screenFrame.size.height / 2.0 { + adjustedHeight = 0.0 + } - let windowSize = window?.frame.size ?? NSSize.zero + let windowSize = window?.frame.size ?? NSSize.zero - // bottom beneath the screen? - if adjustedPoint.y - windowSize.height < screenFrame.minY { - adjustedPoint.y = windowTopLeftPoint.y + adjustedHeight + windowSize.height - } + // bottom beneath the screen? + if adjustedPoint.y - windowSize.height < screenFrame.minY { + adjustedPoint.y = windowTopLeftPoint.y + adjustedHeight + windowSize.height + } - // top over the screen? - if adjustedPoint.y >= screenFrame.maxY { - adjustedPoint.y = screenFrame.maxY - 1.0 - } + // top over the screen? + if adjustedPoint.y >= screenFrame.maxY { + adjustedPoint.y = screenFrame.maxY - 1.0 + } - // right - if adjustedPoint.x + windowSize.width >= screenFrame.maxX { - adjustedPoint.x = screenFrame.maxX - windowSize.width - } + // right + if adjustedPoint.x + windowSize.width >= screenFrame.maxX { + adjustedPoint.x = screenFrame.maxX - windowSize.width + } - // left - if adjustedPoint.x < screenFrame.minX { - adjustedPoint.x = screenFrame.minX - } + // left + if adjustedPoint.x < screenFrame.minX { + adjustedPoint.x = screenFrame.minX + } - window?.setFrameTopLeftPoint(adjustedPoint) - } + window?.setFrameTopLeftPoint(adjustedPoint) + } } diff --git a/Source/UI/CandidateUI/ctlCandidateHorizontal.swift b/Source/UI/CandidateUI/ctlCandidateHorizontal.swift index 88ef3fde..1d3b20f1 100644 --- a/Source/UI/CandidateUI/ctlCandidateHorizontal.swift +++ b/Source/UI/CandidateUI/ctlCandidateHorizontal.swift @@ -27,446 +27,446 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import Cocoa private class HorizontalCandidateView: NSView { - var highlightedIndex: UInt = 0 - var action: Selector? - weak var target: AnyObject? + var highlightedIndex: UInt = 0 + var action: Selector? + weak var target: AnyObject? - private var keyLabels: [String] = [] - private var displayedCandidates: [String] = [] - private var dispCandidatesWithLabels: [String] = [] - private var keyLabelHeight: CGFloat = 0 - private var keyLabelWidth: CGFloat = 0 - private var candidateTextHeight: CGFloat = 0 - private var cellPadding: CGFloat = 0 - private var keyLabelAttrDict: [NSAttributedString.Key: AnyObject] = [:] - private var candidateAttrDict: [NSAttributedString.Key: AnyObject] = [:] - private var candidateWithLabelAttrDict: [NSAttributedString.Key: AnyObject] = [:] - private var elementWidths: [CGFloat] = [] - private var trackingHighlightedIndex: UInt = .max + private var keyLabels: [String] = [] + private var displayedCandidates: [String] = [] + private var dispCandidatesWithLabels: [String] = [] + private var keyLabelHeight: CGFloat = 0 + private var keyLabelWidth: CGFloat = 0 + private var candidateTextHeight: CGFloat = 0 + private var cellPadding: CGFloat = 0 + private var keyLabelAttrDict: [NSAttributedString.Key: AnyObject] = [:] + private var candidateAttrDict: [NSAttributedString.Key: AnyObject] = [:] + private var candidateWithLabelAttrDict: [NSAttributedString.Key: AnyObject] = [:] + private var elementWidths: [CGFloat] = [] + private var trackingHighlightedIndex: UInt = .max - override var isFlipped: Bool { - true - } + override var isFlipped: Bool { + true + } - var sizeForView: NSSize { - var result = NSSize.zero + var sizeForView: NSSize { + var result = NSSize.zero - if !elementWidths.isEmpty { - result.width = elementWidths.reduce(0, +) - result.width += CGFloat(elementWidths.count) - result.height = candidateTextHeight + cellPadding - } - return result - } + if !elementWidths.isEmpty { + result.width = elementWidths.reduce(0, +) + result.width += CGFloat(elementWidths.count) + result.height = candidateTextHeight + cellPadding + } + return result + } - @objc(setKeyLabels:displayedCandidates:) - func set(keyLabels labels: [String], displayedCandidates candidates: [String]) { - let count = min(labels.count, candidates.count) - keyLabels = Array(labels[0.. UInt? { - let location = convert(event.locationInWindow, to: nil) - if !bounds.contains(location) { - return nil - } - var accuWidth: CGFloat = 0.0 - for index in 0.. UInt? { + let location = convert(event.locationInWindow, to: nil) + if !bounds.contains(location) { + return nil + } + var accuWidth: CGFloat = 0.0 + for index in 0..= accuWidth, location.x <= accuWidth + currentWidth { - return UInt(index) - } - accuWidth += currentWidth + 1.0 - } - return nil - } + if location.x >= accuWidth, location.x <= accuWidth + currentWidth { + return UInt(index) + } + accuWidth += currentWidth + 1.0 + } + return nil + } - override func mouseUp(with event: NSEvent) { - trackingHighlightedIndex = highlightedIndex - guard let newIndex = findHitIndex(event: event) else { - return - } - highlightedIndex = newIndex - setNeedsDisplay(bounds) - } + override func mouseUp(with event: NSEvent) { + trackingHighlightedIndex = highlightedIndex + guard let newIndex = findHitIndex(event: event) else { + return + } + highlightedIndex = newIndex + setNeedsDisplay(bounds) + } - override func mouseDown(with event: NSEvent) { - guard let newIndex = findHitIndex(event: event) else { - return - } - var triggerAction = false - if newIndex == highlightedIndex { - triggerAction = true - } else { - highlightedIndex = trackingHighlightedIndex - } + override func mouseDown(with event: NSEvent) { + guard let newIndex = findHitIndex(event: event) else { + return + } + var triggerAction = false + if newIndex == highlightedIndex { + triggerAction = true + } else { + highlightedIndex = trackingHighlightedIndex + } - trackingHighlightedIndex = 0 - setNeedsDisplay(bounds) - if triggerAction { - if let target = target as? NSObject, let action = action { - target.perform(action, with: self) - } - } - } + trackingHighlightedIndex = 0 + setNeedsDisplay(bounds) + if triggerAction { + if let target = target as? NSObject, let action = action { + target.perform(action, with: self) + } + } + } } public class ctlCandidateHorizontal: ctlCandidate { - private var candidateView: HorizontalCandidateView - private var prevPageButton: NSButton - private var nextPageButton: NSButton - private var currentPage: UInt = 0 + private var candidateView: HorizontalCandidateView + private var prevPageButton: NSButton + private var nextPageButton: NSButton + private var currentPage: UInt = 0 - public init() { - var contentRect = NSRect(x: 128.0, y: 128.0, width: 0.0, height: 0.0) - let styleMask: NSWindow.StyleMask = [.nonactivatingPanel] - let panel = NSPanel( - contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false - ) - panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel) + 1) - panel.hasShadow = true - panel.isOpaque = false - panel.backgroundColor = NSColor.clear + public init() { + var contentRect = NSRect(x: 128.0, y: 128.0, width: 0.0, height: 0.0) + let styleMask: NSWindow.StyleMask = [.nonactivatingPanel] + let panel = NSPanel( + contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false + ) + panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel) + 1) + panel.hasShadow = true + panel.isOpaque = false + panel.backgroundColor = NSColor.clear - contentRect.origin = NSPoint.zero - candidateView = HorizontalCandidateView(frame: contentRect) + contentRect.origin = NSPoint.zero + candidateView = HorizontalCandidateView(frame: contentRect) - candidateView.wantsLayer = true - candidateView.layer?.borderColor = - NSColor.selectedMenuItemTextColor.withAlphaComponent(0.30).cgColor - candidateView.layer?.borderWidth = 1.0 - if #available(macOS 10.13, *) { - candidateView.layer?.cornerRadius = 6.0 - } + candidateView.wantsLayer = true + candidateView.layer?.borderColor = + NSColor.selectedMenuItemTextColor.withAlphaComponent(0.30).cgColor + candidateView.layer?.borderWidth = 1.0 + if #available(macOS 10.13, *) { + candidateView.layer?.cornerRadius = 6.0 + } - panel.contentView?.addSubview(candidateView) + panel.contentView?.addSubview(candidateView) - contentRect.size = NSSize(width: 20.0, height: 10.0) // Reduce the button width - let buttonAttribute: [NSAttributedString.Key: Any] = [.font: NSFont.systemFont(ofSize: 9.0)] + contentRect.size = NSSize(width: 20.0, height: 10.0) // Reduce the button width + let buttonAttribute: [NSAttributedString.Key: Any] = [.font: NSFont.systemFont(ofSize: 9.0)] - nextPageButton = NSButton(frame: contentRect) - NSColor.controlBackgroundColor.setFill() - NSBezierPath.fill(nextPageButton.bounds) - nextPageButton.wantsLayer = true - nextPageButton.layer?.masksToBounds = true - nextPageButton.layer?.borderColor = NSColor.clear.cgColor - nextPageButton.layer?.borderWidth = 0.0 - nextPageButton.setButtonType(.momentaryLight) - nextPageButton.bezelStyle = .disclosure - nextPageButton.userInterfaceLayoutDirection = .leftToRight - nextPageButton.attributedTitle = NSMutableAttributedString( - string: " ", attributes: buttonAttribute - ) // Next Page Arrow - prevPageButton = NSButton(frame: contentRect) - NSColor.controlBackgroundColor.setFill() - NSBezierPath.fill(prevPageButton.bounds) - prevPageButton.wantsLayer = true - prevPageButton.layer?.masksToBounds = true - prevPageButton.layer?.borderColor = NSColor.clear.cgColor - prevPageButton.layer?.borderWidth = 0.0 - prevPageButton.setButtonType(.momentaryLight) - prevPageButton.bezelStyle = .disclosure - prevPageButton.userInterfaceLayoutDirection = .rightToLeft - prevPageButton.attributedTitle = NSMutableAttributedString( - string: " ", attributes: buttonAttribute - ) // Previous Page Arrow - panel.contentView?.addSubview(nextPageButton) - panel.contentView?.addSubview(prevPageButton) + nextPageButton = NSButton(frame: contentRect) + NSColor.controlBackgroundColor.setFill() + NSBezierPath.fill(nextPageButton.bounds) + nextPageButton.wantsLayer = true + nextPageButton.layer?.masksToBounds = true + nextPageButton.layer?.borderColor = NSColor.clear.cgColor + nextPageButton.layer?.borderWidth = 0.0 + nextPageButton.setButtonType(.momentaryLight) + nextPageButton.bezelStyle = .disclosure + nextPageButton.userInterfaceLayoutDirection = .leftToRight + nextPageButton.attributedTitle = NSMutableAttributedString( + string: " ", attributes: buttonAttribute + ) // Next Page Arrow + prevPageButton = NSButton(frame: contentRect) + NSColor.controlBackgroundColor.setFill() + NSBezierPath.fill(prevPageButton.bounds) + prevPageButton.wantsLayer = true + prevPageButton.layer?.masksToBounds = true + prevPageButton.layer?.borderColor = NSColor.clear.cgColor + prevPageButton.layer?.borderWidth = 0.0 + prevPageButton.setButtonType(.momentaryLight) + prevPageButton.bezelStyle = .disclosure + prevPageButton.userInterfaceLayoutDirection = .rightToLeft + prevPageButton.attributedTitle = NSMutableAttributedString( + string: " ", attributes: buttonAttribute + ) // Previous Page Arrow + panel.contentView?.addSubview(nextPageButton) + panel.contentView?.addSubview(prevPageButton) - super.init(window: panel) + super.init(window: panel) - candidateView.target = self - candidateView.action = #selector(candidateViewMouseDidClick(_:)) + candidateView.target = self + candidateView.action = #selector(candidateViewMouseDidClick(_:)) - nextPageButton.target = self - nextPageButton.action = #selector(pageButtonAction(_:)) + nextPageButton.target = self + nextPageButton.action = #selector(pageButtonAction(_:)) - prevPageButton.target = self - prevPageButton.action = #selector(pageButtonAction(_:)) - } + prevPageButton.target = self + prevPageButton.action = #selector(pageButtonAction(_:)) + } - @available(*, unavailable) - required init?(coder _: NSCoder) { - fatalError("init(coder:) has not been implemented") - } + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } - override public func reloadData() { - candidateView.highlightedIndex = 0 - currentPage = 0 - layoutCandidateView() - } + override public func reloadData() { + candidateView.highlightedIndex = 0 + currentPage = 0 + layoutCandidateView() + } - override public func showNextPage() -> Bool { - guard delegate != nil else { return false } - if pageCount == 1 { return highlightNextCandidate() } - currentPage = (currentPage + 1 >= pageCount) ? 0 : currentPage + 1 - candidateView.highlightedIndex = 0 - layoutCandidateView() - return true - } + override public func showNextPage() -> Bool { + guard delegate != nil else { return false } + if pageCount == 1 { return highlightNextCandidate() } + currentPage = (currentPage + 1 >= pageCount) ? 0 : currentPage + 1 + candidateView.highlightedIndex = 0 + layoutCandidateView() + return true + } - override public func showPreviousPage() -> Bool { - guard delegate != nil else { return false } - if pageCount == 1 { return highlightPreviousCandidate() } - currentPage = (currentPage == 0) ? pageCount - 1 : currentPage - 1 - candidateView.highlightedIndex = 0 - layoutCandidateView() - return true - } + override public func showPreviousPage() -> Bool { + guard delegate != nil else { return false } + if pageCount == 1 { return highlightPreviousCandidate() } + currentPage = (currentPage == 0) ? pageCount - 1 : currentPage - 1 + candidateView.highlightedIndex = 0 + layoutCandidateView() + return true + } - override public func highlightNextCandidate() -> Bool { - guard let delegate = delegate else { return false } - selectedCandidateIndex = - (selectedCandidateIndex + 1 >= delegate.candidateCountForController(self)) - ? 0 : selectedCandidateIndex + 1 - return true - } + override public func highlightNextCandidate() -> Bool { + guard let delegate = delegate else { return false } + selectedCandidateIndex = + (selectedCandidateIndex + 1 >= delegate.candidateCountForController(self)) + ? 0 : selectedCandidateIndex + 1 + return true + } - override public func highlightPreviousCandidate() -> Bool { - guard let delegate = delegate else { return false } - selectedCandidateIndex = - (selectedCandidateIndex == 0) - ? delegate.candidateCountForController(self) - 1 : selectedCandidateIndex - 1 - return true - } + override public func highlightPreviousCandidate() -> Bool { + guard let delegate = delegate else { return false } + selectedCandidateIndex = + (selectedCandidateIndex == 0) + ? delegate.candidateCountForController(self) - 1 : selectedCandidateIndex - 1 + return true + } - override public func candidateIndexAtKeyLabelIndex(_ index: UInt) -> UInt { - guard let delegate = delegate else { - return UInt.max - } + override public func candidateIndexAtKeyLabelIndex(_ index: UInt) -> UInt { + guard let delegate = delegate else { + return UInt.max + } - let result = currentPage * UInt(keyLabels.count) + index - return result < delegate.candidateCountForController(self) ? result : UInt.max - } + let result = currentPage * UInt(keyLabels.count) + index + return result < delegate.candidateCountForController(self) ? result : UInt.max + } - override public var selectedCandidateIndex: UInt { - get { - currentPage * UInt(keyLabels.count) + candidateView.highlightedIndex - } - set { - guard let delegate = delegate else { - return - } - let keyLabelCount = UInt(keyLabels.count) - if newValue < delegate.candidateCountForController(self) { - currentPage = newValue / keyLabelCount - candidateView.highlightedIndex = newValue % keyLabelCount - layoutCandidateView() - } - } - } + override public var selectedCandidateIndex: UInt { + get { + currentPage * UInt(keyLabels.count) + candidateView.highlightedIndex + } + set { + guard let delegate = delegate else { + return + } + let keyLabelCount = UInt(keyLabels.count) + if newValue < delegate.candidateCountForController(self) { + currentPage = newValue / keyLabelCount + candidateView.highlightedIndex = newValue % keyLabelCount + layoutCandidateView() + } + } + } } extension ctlCandidateHorizontal { - private var pageCount: UInt { - guard let delegate = delegate else { - return 0 - } - let totalCount = delegate.candidateCountForController(self) - let keyLabelCount = UInt(keyLabels.count) - return totalCount / keyLabelCount + ((totalCount % keyLabelCount) != 0 ? 1 : 0) - } + private var pageCount: UInt { + guard let delegate = delegate else { + return 0 + } + let totalCount = delegate.candidateCountForController(self) + let keyLabelCount = UInt(keyLabels.count) + return totalCount / keyLabelCount + ((totalCount % keyLabelCount) != 0 ? 1 : 0) + } - private func layoutCandidateView() { - guard let delegate = delegate else { - return - } + private func layoutCandidateView() { + guard let delegate = delegate else { + return + } - candidateView.set(keyLabelFont: keyLabelFont, candidateFont: candidateFont) - var candidates = [String]() - let count = delegate.candidateCountForController(self) - let keyLabelCount = UInt(keyLabels.count) + candidateView.set(keyLabelFont: keyLabelFont, candidateFont: candidateFont) + var candidates = [String]() + let count = delegate.candidateCountForController(self) + let keyLabelCount = UInt(keyLabels.count) - let begin = currentPage * keyLabelCount - for index in begin.. 1, mgrPrefs.showPageButtonsInCandidateWindow { - var buttonRect = nextPageButton.frame - let spacing: CGFloat = 0.0 + if pageCount > 1, mgrPrefs.showPageButtonsInCandidateWindow { + var buttonRect = nextPageButton.frame + let spacing: CGFloat = 0.0 - buttonRect.size.height = floor(newSize.height / 2) + buttonRect.size.height = floor(newSize.height / 2) - let buttonOriginY = (newSize.height - (buttonRect.size.height * 2.0 + spacing)) / 2.0 - buttonRect.origin = NSPoint(x: newSize.width, y: buttonOriginY) - nextPageButton.frame = buttonRect + let buttonOriginY = (newSize.height - (buttonRect.size.height * 2.0 + spacing)) / 2.0 + buttonRect.origin = NSPoint(x: newSize.width, y: buttonOriginY) + nextPageButton.frame = buttonRect - buttonRect.origin = NSPoint( - x: newSize.width, y: buttonOriginY + buttonRect.size.height + spacing - ) - prevPageButton.frame = buttonRect + buttonRect.origin = NSPoint( + x: newSize.width, y: buttonOriginY + buttonRect.size.height + spacing + ) + prevPageButton.frame = buttonRect - newSize.width += 20 - nextPageButton.isHidden = false - prevPageButton.isHidden = false - } else { - nextPageButton.isHidden = true - prevPageButton.isHidden = true - } + newSize.width += 20 + nextPageButton.isHidden = false + prevPageButton.isHidden = false + } else { + nextPageButton.isHidden = true + prevPageButton.isHidden = true + } - frameRect = window?.frame ?? NSRect.zero + frameRect = window?.frame ?? NSRect.zero - let topLeftPoint = NSPoint(x: frameRect.origin.x, y: frameRect.origin.y + frameRect.size.height) - frameRect.size = newSize - frameRect.origin = NSPoint(x: topLeftPoint.x, y: topLeftPoint.y - frameRect.size.height) - window?.setFrame(frameRect, display: false) - candidateView.setNeedsDisplay(candidateView.bounds) - } + let topLeftPoint = NSPoint(x: frameRect.origin.x, y: frameRect.origin.y + frameRect.size.height) + frameRect.size = newSize + frameRect.origin = NSPoint(x: topLeftPoint.x, y: topLeftPoint.y - frameRect.size.height) + window?.setFrame(frameRect, display: false) + candidateView.setNeedsDisplay(candidateView.bounds) + } - @objc private func pageButtonAction(_ sender: Any) { - guard let sender = sender as? NSButton else { - return - } - if sender == nextPageButton { - _ = showNextPage() - } else if sender == prevPageButton { - _ = showPreviousPage() - } - } + @objc private func pageButtonAction(_ sender: Any) { + guard let sender = sender as? NSButton else { + return + } + if sender == nextPageButton { + _ = showNextPage() + } else if sender == prevPageButton { + _ = showPreviousPage() + } + } - @objc private func candidateViewMouseDidClick(_: Any) { - delegate?.ctlCandidate(self, didSelectCandidateAtIndex: selectedCandidateIndex) - } + @objc private func candidateViewMouseDidClick(_: Any) { + delegate?.ctlCandidate(self, didSelectCandidateAtIndex: selectedCandidateIndex) + } } diff --git a/Source/UI/CandidateUI/ctlCandidateVertical.swift b/Source/UI/CandidateUI/ctlCandidateVertical.swift index b2ed0601..0084b6c3 100644 --- a/Source/UI/CandidateUI/ctlCandidateVertical.swift +++ b/Source/UI/CandidateUI/ctlCandidateVertical.swift @@ -27,450 +27,450 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import Cocoa private class VerticalCandidateView: NSView { - var highlightedIndex: UInt = 0 - var action: Selector? - weak var target: AnyObject? + var highlightedIndex: UInt = 0 + var action: Selector? + weak var target: AnyObject? - private var keyLabels: [String] = [] - private var displayedCandidates: [String] = [] - private var dispCandidatesWithLabels: [String] = [] - private var keyLabelHeight: CGFloat = 0 - private var keyLabelWidth: CGFloat = 0 - private var candidateTextHeight: CGFloat = 0 - private var cellPadding: CGFloat = 0 - private var keyLabelAttrDict: [NSAttributedString.Key: AnyObject] = [:] - private var candidateAttrDict: [NSAttributedString.Key: AnyObject] = [:] - private var candidateWithLabelAttrDict: [NSAttributedString.Key: AnyObject] = [:] - private var windowWidth: CGFloat = 0 - private var elementWidths: [CGFloat] = [] - private var elementHeights: [CGFloat] = [] - private var trackingHighlightedIndex: UInt = .max + private var keyLabels: [String] = [] + private var displayedCandidates: [String] = [] + private var dispCandidatesWithLabels: [String] = [] + private var keyLabelHeight: CGFloat = 0 + private var keyLabelWidth: CGFloat = 0 + private var candidateTextHeight: CGFloat = 0 + private var cellPadding: CGFloat = 0 + private var keyLabelAttrDict: [NSAttributedString.Key: AnyObject] = [:] + private var candidateAttrDict: [NSAttributedString.Key: AnyObject] = [:] + private var candidateWithLabelAttrDict: [NSAttributedString.Key: AnyObject] = [:] + private var windowWidth: CGFloat = 0 + private var elementWidths: [CGFloat] = [] + private var elementHeights: [CGFloat] = [] + private var trackingHighlightedIndex: UInt = .max - override var isFlipped: Bool { - true - } + override var isFlipped: Bool { + true + } - var sizeForView: NSSize { - var result = NSSize.zero + var sizeForView: NSSize { + var result = NSSize.zero - if !elementWidths.isEmpty { - result.width = windowWidth - result.height = elementHeights.reduce(0, +) - } - return result - } + if !elementWidths.isEmpty { + result.width = windowWidth + result.height = elementHeights.reduce(0, +) + } + return result + } - @objc(setKeyLabels:displayedCandidates:) - func set(keyLabels labels: [String], displayedCandidates candidates: [String]) { - let count = min(labels.count, candidates.count) - keyLabels = Array(labels[0.. UInt? { - let location = convert(event.locationInWindow, to: nil) - if !bounds.contains(location) { - return nil - } - var accuHeight: CGFloat = 0.0 - for index in 0.. UInt? { + let location = convert(event.locationInWindow, to: nil) + if !bounds.contains(location) { + return nil + } + var accuHeight: CGFloat = 0.0 + for index in 0..= accuHeight, location.y <= accuHeight + currentHeight { - return UInt(index) - } - accuHeight += currentHeight - } - return nil - } + if location.y >= accuHeight, location.y <= accuHeight + currentHeight { + return UInt(index) + } + accuHeight += currentHeight + } + return nil + } - override func mouseUp(with event: NSEvent) { - trackingHighlightedIndex = highlightedIndex - guard let newIndex = findHitIndex(event: event) else { - return - } - highlightedIndex = newIndex - setNeedsDisplay(bounds) - } + override func mouseUp(with event: NSEvent) { + trackingHighlightedIndex = highlightedIndex + guard let newIndex = findHitIndex(event: event) else { + return + } + highlightedIndex = newIndex + setNeedsDisplay(bounds) + } - override func mouseDown(with event: NSEvent) { - guard let newIndex = findHitIndex(event: event) else { - return - } - var triggerAction = false - if newIndex == highlightedIndex { - triggerAction = true - } else { - highlightedIndex = trackingHighlightedIndex - } + override func mouseDown(with event: NSEvent) { + guard let newIndex = findHitIndex(event: event) else { + return + } + var triggerAction = false + if newIndex == highlightedIndex { + triggerAction = true + } else { + highlightedIndex = trackingHighlightedIndex + } - trackingHighlightedIndex = 0 - setNeedsDisplay(bounds) - if triggerAction { - if let target = target as? NSObject, let action = action { - target.perform(action, with: self) - } - } - } + trackingHighlightedIndex = 0 + setNeedsDisplay(bounds) + if triggerAction { + if let target = target as? NSObject, let action = action { + target.perform(action, with: self) + } + } + } } public class ctlCandidateVertical: ctlCandidate { - private var candidateView: VerticalCandidateView - private var prevPageButton: NSButton - private var nextPageButton: NSButton - private var currentPage: UInt = 0 + private var candidateView: VerticalCandidateView + private var prevPageButton: NSButton + private var nextPageButton: NSButton + private var currentPage: UInt = 0 - public init() { - var contentRect = NSRect(x: 128.0, y: 128.0, width: 0.0, height: 0.0) - let styleMask: NSWindow.StyleMask = [.nonactivatingPanel] - let panel = NSPanel( - contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false - ) - panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel) + 1) - panel.hasShadow = true - panel.isOpaque = false - panel.backgroundColor = NSColor.clear + public init() { + var contentRect = NSRect(x: 128.0, y: 128.0, width: 0.0, height: 0.0) + let styleMask: NSWindow.StyleMask = [.nonactivatingPanel] + let panel = NSPanel( + contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false + ) + panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel) + 1) + panel.hasShadow = true + panel.isOpaque = false + panel.backgroundColor = NSColor.clear - contentRect.origin = NSPoint.zero - candidateView = VerticalCandidateView(frame: contentRect) + contentRect.origin = NSPoint.zero + candidateView = VerticalCandidateView(frame: contentRect) - candidateView.wantsLayer = true - candidateView.layer?.borderColor = - NSColor.selectedMenuItemTextColor.withAlphaComponent(0.30).cgColor - candidateView.layer?.borderWidth = 1.0 - if #available(macOS 10.13, *) { - candidateView.layer?.cornerRadius = 6.0 - } + candidateView.wantsLayer = true + candidateView.layer?.borderColor = + NSColor.selectedMenuItemTextColor.withAlphaComponent(0.30).cgColor + candidateView.layer?.borderWidth = 1.0 + if #available(macOS 10.13, *) { + candidateView.layer?.cornerRadius = 6.0 + } - panel.contentView?.addSubview(candidateView) + panel.contentView?.addSubview(candidateView) - contentRect.size = NSSize(width: 20.0, height: 10.0) // Reduce the button width - let buttonAttribute: [NSAttributedString.Key: Any] = [.font: NSFont.systemFont(ofSize: 9.0)] + contentRect.size = NSSize(width: 20.0, height: 10.0) // Reduce the button width + let buttonAttribute: [NSAttributedString.Key: Any] = [.font: NSFont.systemFont(ofSize: 9.0)] - nextPageButton = NSButton(frame: contentRect) - NSColor.controlBackgroundColor.setFill() - NSBezierPath.fill(nextPageButton.bounds) - nextPageButton.wantsLayer = true - nextPageButton.layer?.masksToBounds = true - nextPageButton.layer?.borderColor = NSColor.clear.cgColor - nextPageButton.layer?.borderWidth = 0.0 - nextPageButton.setButtonType(.momentaryLight) - nextPageButton.bezelStyle = .disclosure - nextPageButton.userInterfaceLayoutDirection = .leftToRight - nextPageButton.attributedTitle = NSMutableAttributedString( - string: " ", attributes: buttonAttribute - ) // Next Page Arrow - prevPageButton = NSButton(frame: contentRect) - NSColor.controlBackgroundColor.setFill() - NSBezierPath.fill(prevPageButton.bounds) - prevPageButton.wantsLayer = true - prevPageButton.layer?.masksToBounds = true - prevPageButton.layer?.borderColor = NSColor.clear.cgColor - prevPageButton.layer?.borderWidth = 0.0 - prevPageButton.setButtonType(.momentaryLight) - prevPageButton.bezelStyle = .disclosure - prevPageButton.userInterfaceLayoutDirection = .rightToLeft - prevPageButton.attributedTitle = NSMutableAttributedString( - string: " ", attributes: buttonAttribute - ) // Previous Page Arrow - panel.contentView?.addSubview(nextPageButton) - panel.contentView?.addSubview(prevPageButton) + nextPageButton = NSButton(frame: contentRect) + NSColor.controlBackgroundColor.setFill() + NSBezierPath.fill(nextPageButton.bounds) + nextPageButton.wantsLayer = true + nextPageButton.layer?.masksToBounds = true + nextPageButton.layer?.borderColor = NSColor.clear.cgColor + nextPageButton.layer?.borderWidth = 0.0 + nextPageButton.setButtonType(.momentaryLight) + nextPageButton.bezelStyle = .disclosure + nextPageButton.userInterfaceLayoutDirection = .leftToRight + nextPageButton.attributedTitle = NSMutableAttributedString( + string: " ", attributes: buttonAttribute + ) // Next Page Arrow + prevPageButton = NSButton(frame: contentRect) + NSColor.controlBackgroundColor.setFill() + NSBezierPath.fill(prevPageButton.bounds) + prevPageButton.wantsLayer = true + prevPageButton.layer?.masksToBounds = true + prevPageButton.layer?.borderColor = NSColor.clear.cgColor + prevPageButton.layer?.borderWidth = 0.0 + prevPageButton.setButtonType(.momentaryLight) + prevPageButton.bezelStyle = .disclosure + prevPageButton.userInterfaceLayoutDirection = .rightToLeft + prevPageButton.attributedTitle = NSMutableAttributedString( + string: " ", attributes: buttonAttribute + ) // Previous Page Arrow + panel.contentView?.addSubview(nextPageButton) + panel.contentView?.addSubview(prevPageButton) - super.init(window: panel) + super.init(window: panel) - candidateView.target = self - candidateView.action = #selector(candidateViewMouseDidClick(_:)) + candidateView.target = self + candidateView.action = #selector(candidateViewMouseDidClick(_:)) - nextPageButton.target = self - nextPageButton.action = #selector(pageButtonAction(_:)) + nextPageButton.target = self + nextPageButton.action = #selector(pageButtonAction(_:)) - prevPageButton.target = self - prevPageButton.action = #selector(pageButtonAction(_:)) - } + prevPageButton.target = self + prevPageButton.action = #selector(pageButtonAction(_:)) + } - @available(*, unavailable) - required init?(coder _: NSCoder) { - fatalError("init(coder:) has not been implemented") - } + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } - override public func reloadData() { - candidateView.highlightedIndex = 0 - currentPage = 0 - layoutCandidateView() - } + override public func reloadData() { + candidateView.highlightedIndex = 0 + currentPage = 0 + layoutCandidateView() + } - override public func showNextPage() -> Bool { - guard delegate != nil else { return false } - if pageCount == 1 { return highlightNextCandidate() } - currentPage = (currentPage + 1 >= pageCount) ? 0 : currentPage + 1 - candidateView.highlightedIndex = 0 - layoutCandidateView() - return true - } + override public func showNextPage() -> Bool { + guard delegate != nil else { return false } + if pageCount == 1 { return highlightNextCandidate() } + currentPage = (currentPage + 1 >= pageCount) ? 0 : currentPage + 1 + candidateView.highlightedIndex = 0 + layoutCandidateView() + return true + } - override public func showPreviousPage() -> Bool { - guard delegate != nil else { return false } - if pageCount == 1 { return highlightPreviousCandidate() } - currentPage = (currentPage == 0) ? pageCount - 1 : currentPage - 1 - candidateView.highlightedIndex = 0 - layoutCandidateView() - return true - } + override public func showPreviousPage() -> Bool { + guard delegate != nil else { return false } + if pageCount == 1 { return highlightPreviousCandidate() } + currentPage = (currentPage == 0) ? pageCount - 1 : currentPage - 1 + candidateView.highlightedIndex = 0 + layoutCandidateView() + return true + } - override public func highlightNextCandidate() -> Bool { - guard let delegate = delegate else { return false } - selectedCandidateIndex = - (selectedCandidateIndex + 1 >= delegate.candidateCountForController(self)) - ? 0 : selectedCandidateIndex + 1 - return true - } + override public func highlightNextCandidate() -> Bool { + guard let delegate = delegate else { return false } + selectedCandidateIndex = + (selectedCandidateIndex + 1 >= delegate.candidateCountForController(self)) + ? 0 : selectedCandidateIndex + 1 + return true + } - override public func highlightPreviousCandidate() -> Bool { - guard let delegate = delegate else { return false } - selectedCandidateIndex = - (selectedCandidateIndex == 0) - ? delegate.candidateCountForController(self) - 1 : selectedCandidateIndex - 1 - return true - } + override public func highlightPreviousCandidate() -> Bool { + guard let delegate = delegate else { return false } + selectedCandidateIndex = + (selectedCandidateIndex == 0) + ? delegate.candidateCountForController(self) - 1 : selectedCandidateIndex - 1 + return true + } - override public func candidateIndexAtKeyLabelIndex(_ index: UInt) -> UInt { - guard let delegate = delegate else { - return UInt.max - } + override public func candidateIndexAtKeyLabelIndex(_ index: UInt) -> UInt { + guard let delegate = delegate else { + return UInt.max + } - let result = currentPage * UInt(keyLabels.count) + index - return result < delegate.candidateCountForController(self) ? result : UInt.max - } + let result = currentPage * UInt(keyLabels.count) + index + return result < delegate.candidateCountForController(self) ? result : UInt.max + } - override public var selectedCandidateIndex: UInt { - get { - currentPage * UInt(keyLabels.count) + candidateView.highlightedIndex - } - set { - guard let delegate = delegate else { - return - } - let keyLabelCount = UInt(keyLabels.count) - if newValue < delegate.candidateCountForController(self) { - currentPage = newValue / keyLabelCount - candidateView.highlightedIndex = newValue % keyLabelCount - layoutCandidateView() - } - } - } + override public var selectedCandidateIndex: UInt { + get { + currentPage * UInt(keyLabels.count) + candidateView.highlightedIndex + } + set { + guard let delegate = delegate else { + return + } + let keyLabelCount = UInt(keyLabels.count) + if newValue < delegate.candidateCountForController(self) { + currentPage = newValue / keyLabelCount + candidateView.highlightedIndex = newValue % keyLabelCount + layoutCandidateView() + } + } + } } extension ctlCandidateVertical { - private var pageCount: UInt { - guard let delegate = delegate else { - return 0 - } - let totalCount = delegate.candidateCountForController(self) - let keyLabelCount = UInt(keyLabels.count) - return totalCount / keyLabelCount + ((totalCount % keyLabelCount) != 0 ? 1 : 0) - } + private var pageCount: UInt { + guard let delegate = delegate else { + return 0 + } + let totalCount = delegate.candidateCountForController(self) + let keyLabelCount = UInt(keyLabels.count) + return totalCount / keyLabelCount + ((totalCount % keyLabelCount) != 0 ? 1 : 0) + } - private func layoutCandidateView() { - guard let delegate = delegate else { - return - } + private func layoutCandidateView() { + guard let delegate = delegate else { + return + } - candidateView.set(keyLabelFont: keyLabelFont, candidateFont: candidateFont) - var candidates = [String]() - let count = delegate.candidateCountForController(self) - let keyLabelCount = UInt(keyLabels.count) + candidateView.set(keyLabelFont: keyLabelFont, candidateFont: candidateFont) + var candidates = [String]() + let count = delegate.candidateCountForController(self) + let keyLabelCount = UInt(keyLabels.count) - let begin = currentPage * keyLabelCount - for index in begin.. 1, mgrPrefs.showPageButtonsInCandidateWindow { - var buttonRect = nextPageButton.frame - let spacing: CGFloat = 0.0 + if pageCount > 1, mgrPrefs.showPageButtonsInCandidateWindow { + var buttonRect = nextPageButton.frame + let spacing: CGFloat = 0.0 - // buttonRect.size.height = floor(candidateTextHeight + cellPadding / 2) + // buttonRect.size.height = floor(candidateTextHeight + cellPadding / 2) - let buttonOriginY = (newSize.height - (buttonRect.size.height * 2.0 + spacing)) // / 2.0 - buttonRect.origin = NSPoint(x: newSize.width, y: buttonOriginY) - nextPageButton.frame = buttonRect + let buttonOriginY = (newSize.height - (buttonRect.size.height * 2.0 + spacing)) // / 2.0 + buttonRect.origin = NSPoint(x: newSize.width, y: buttonOriginY) + nextPageButton.frame = buttonRect - buttonRect.origin = NSPoint( - x: newSize.width, y: buttonOriginY + buttonRect.size.height + spacing - ) - prevPageButton.frame = buttonRect + buttonRect.origin = NSPoint( + x: newSize.width, y: buttonOriginY + buttonRect.size.height + spacing + ) + prevPageButton.frame = buttonRect - newSize.width += 20 - nextPageButton.isHidden = false - prevPageButton.isHidden = false - } else { - nextPageButton.isHidden = true - prevPageButton.isHidden = true - } + newSize.width += 20 + nextPageButton.isHidden = false + prevPageButton.isHidden = false + } else { + nextPageButton.isHidden = true + prevPageButton.isHidden = true + } - frameRect = window?.frame ?? NSRect.zero + frameRect = window?.frame ?? NSRect.zero - let topLeftPoint = NSPoint(x: frameRect.origin.x, y: frameRect.origin.y + frameRect.size.height) - frameRect.size = newSize - frameRect.origin = NSPoint(x: topLeftPoint.x, y: topLeftPoint.y - frameRect.size.height) - window?.setFrame(frameRect, display: false) - candidateView.setNeedsDisplay(candidateView.bounds) - } + let topLeftPoint = NSPoint(x: frameRect.origin.x, y: frameRect.origin.y + frameRect.size.height) + frameRect.size = newSize + frameRect.origin = NSPoint(x: topLeftPoint.x, y: topLeftPoint.y - frameRect.size.height) + window?.setFrame(frameRect, display: false) + candidateView.setNeedsDisplay(candidateView.bounds) + } - @objc private func pageButtonAction(_ sender: Any) { - guard let sender = sender as? NSButton else { - return - } - if sender == nextPageButton { - _ = showNextPage() - } else if sender == prevPageButton { - _ = showPreviousPage() - } - } + @objc private func pageButtonAction(_ sender: Any) { + guard let sender = sender as? NSButton else { + return + } + if sender == nextPageButton { + _ = showNextPage() + } else if sender == prevPageButton { + _ = showPreviousPage() + } + } - @objc private func candidateViewMouseDidClick(_: Any) { - delegate?.ctlCandidate(self, didSelectCandidateAtIndex: selectedCandidateIndex) - } + @objc private func candidateViewMouseDidClick(_: Any) { + delegate?.ctlCandidate(self, didSelectCandidateAtIndex: selectedCandidateIndex) + } } diff --git a/Source/UI/NotifierUI/NotifierController.swift b/Source/UI/NotifierUI/NotifierController.swift index 6dff0825..c286979d 100644 --- a/Source/UI/NotifierUI/NotifierController.swift +++ b/Source/UI/NotifierUI/NotifierController.swift @@ -27,199 +27,199 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import Cocoa private protocol NotifierWindowDelegate: AnyObject { - func windowDidBecomeClicked(_ window: NotifierWindow) + func windowDidBecomeClicked(_ window: NotifierWindow) } private class NotifierWindow: NSWindow { - weak var clickDelegate: NotifierWindowDelegate? + weak var clickDelegate: NotifierWindowDelegate? - override func mouseDown(with _: NSEvent) { - clickDelegate?.windowDidBecomeClicked(self) - } + override func mouseDown(with _: NSEvent) { + clickDelegate?.windowDidBecomeClicked(self) + } } private let kWindowWidth: CGFloat = 213.0 private let kWindowHeight: CGFloat = 60.0 public class NotifierController: NSWindowController, NotifierWindowDelegate { - private var messageTextField: NSTextField + private var messageTextField: NSTextField - private var message: String = "" { - didSet { - let paraStyle = NSMutableParagraphStyle() - paraStyle.setParagraphStyle(NSParagraphStyle.default) - paraStyle.alignment = .center - let attr: [NSAttributedString.Key: AnyObject] = [ - .foregroundColor: foregroundColor, - .font: NSFont.boldSystemFont(ofSize: NSFont.systemFontSize(for: .regular)), - .paragraphStyle: paraStyle, - ] - let attrString = NSAttributedString(string: message, attributes: attr) - messageTextField.attributedStringValue = attrString - let width = window?.frame.width ?? kWindowWidth - let rect = attrString.boundingRect( - with: NSSize(width: width, height: 1600), options: .usesLineFragmentOrigin - ) - let height = rect.height - let x = messageTextField.frame.origin.x - let y = ((window?.frame.height ?? kWindowHeight) - height) / 2 - let newFrame = NSRect(x: x, y: y, width: width, height: height) - messageTextField.frame = newFrame - } - } + private var message: String = "" { + didSet { + let paraStyle = NSMutableParagraphStyle() + paraStyle.setParagraphStyle(NSParagraphStyle.default) + paraStyle.alignment = .center + let attr: [NSAttributedString.Key: AnyObject] = [ + .foregroundColor: foregroundColor, + .font: NSFont.boldSystemFont(ofSize: NSFont.systemFontSize(for: .regular)), + .paragraphStyle: paraStyle, + ] + let attrString = NSAttributedString(string: message, attributes: attr) + messageTextField.attributedStringValue = attrString + let width = window?.frame.width ?? kWindowWidth + let rect = attrString.boundingRect( + with: NSSize(width: width, height: 1600), options: .usesLineFragmentOrigin + ) + let height = rect.height + let x = messageTextField.frame.origin.x + let y = ((window?.frame.height ?? kWindowHeight) - height) / 2 + let newFrame = NSRect(x: x, y: y, width: width, height: height) + messageTextField.frame = newFrame + } + } - private var shouldStay: Bool = false - private var backgroundColor: NSColor = .textBackgroundColor { - didSet { - window?.backgroundColor = backgroundColor - } - } + private var shouldStay: Bool = false + private var backgroundColor: NSColor = .textBackgroundColor { + didSet { + window?.backgroundColor = backgroundColor + } + } - private var foregroundColor: NSColor = .controlTextColor { - didSet { - messageTextField.textColor = foregroundColor - } - } + private var foregroundColor: NSColor = .controlTextColor { + didSet { + messageTextField.textColor = foregroundColor + } + } - private var waitTimer: Timer? - private var fadeTimer: Timer? + private var waitTimer: Timer? + private var fadeTimer: Timer? - private static var instanceCount = 0 - private static var lastLocation = NSPoint.zero + private static var instanceCount = 0 + private static var lastLocation = NSPoint.zero - @objc public static func notify(message: String, stay: Bool = false) { - let controller = NotifierController() - controller.message = message - controller.shouldStay = stay - controller.show() - } + public static func notify(message: String, stay: Bool = false) { + let controller = NotifierController() + controller.message = message + controller.shouldStay = stay + controller.show() + } - private static func increaseInstanceCount() { - instanceCount += 1 - } + private static func increaseInstanceCount() { + instanceCount += 1 + } - private static func decreaseInstanceCount() { - instanceCount -= 1 - if instanceCount < 0 { - instanceCount = 0 - } - } + private static func decreaseInstanceCount() { + instanceCount -= 1 + if instanceCount < 0 { + instanceCount = 0 + } + } - private init() { - let screenRect = NSScreen.main?.visibleFrame ?? NSRect.zero - let contentRect = NSRect(x: 0, y: 0, width: kWindowWidth, height: kWindowHeight) - var windowRect = contentRect - windowRect.origin.x = screenRect.maxX - windowRect.width - 10 - windowRect.origin.y = screenRect.maxY - windowRect.height - 10 - let styleMask: NSWindow.StyleMask = [.fullSizeContentView, .titled] + private init() { + let screenRect = NSScreen.main?.visibleFrame ?? NSRect.zero + let contentRect = NSRect(x: 0, y: 0, width: kWindowWidth, height: kWindowHeight) + var windowRect = contentRect + windowRect.origin.x = screenRect.maxX - windowRect.width - 10 + windowRect.origin.y = screenRect.maxY - windowRect.height - 10 + let styleMask: NSWindow.StyleMask = [.fullSizeContentView, .titled] - let transparentVisualEffect = NSVisualEffectView() - transparentVisualEffect.blendingMode = .behindWindow - transparentVisualEffect.state = .active + let transparentVisualEffect = NSVisualEffectView() + transparentVisualEffect.blendingMode = .behindWindow + transparentVisualEffect.state = .active - let panel = NotifierWindow( - contentRect: windowRect, styleMask: styleMask, backing: .buffered, defer: false - ) - panel.contentView = transparentVisualEffect - panel.isMovableByWindowBackground = true - panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel)) - panel.hasShadow = true - panel.backgroundColor = backgroundColor - panel.title = "" - panel.titlebarAppearsTransparent = true - panel.titleVisibility = .hidden - panel.showsToolbarButton = false - panel.standardWindowButton(NSWindow.ButtonType.zoomButton)?.isHidden = true - panel.standardWindowButton(NSWindow.ButtonType.miniaturizeButton)?.isHidden = true - panel.standardWindowButton(NSWindow.ButtonType.closeButton)?.isHidden = true - panel.standardWindowButton(NSWindow.ButtonType.zoomButton)?.isHidden = true + let panel = NotifierWindow( + contentRect: windowRect, styleMask: styleMask, backing: .buffered, defer: false + ) + panel.contentView = transparentVisualEffect + panel.isMovableByWindowBackground = true + panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel)) + panel.hasShadow = true + panel.backgroundColor = backgroundColor + panel.title = "" + panel.titlebarAppearsTransparent = true + panel.titleVisibility = .hidden + panel.showsToolbarButton = false + panel.standardWindowButton(NSWindow.ButtonType.zoomButton)?.isHidden = true + panel.standardWindowButton(NSWindow.ButtonType.miniaturizeButton)?.isHidden = true + panel.standardWindowButton(NSWindow.ButtonType.closeButton)?.isHidden = true + panel.standardWindowButton(NSWindow.ButtonType.zoomButton)?.isHidden = true - messageTextField = NSTextField() - messageTextField.frame = contentRect - messageTextField.isEditable = false - messageTextField.isSelectable = false - messageTextField.isBezeled = false - messageTextField.textColor = foregroundColor - messageTextField.drawsBackground = false - messageTextField.font = .boldSystemFont(ofSize: NSFont.systemFontSize(for: .regular)) - panel.contentView?.addSubview(messageTextField) + messageTextField = NSTextField() + messageTextField.frame = contentRect + messageTextField.isEditable = false + messageTextField.isSelectable = false + messageTextField.isBezeled = false + messageTextField.textColor = foregroundColor + messageTextField.drawsBackground = false + messageTextField.font = .boldSystemFont(ofSize: NSFont.systemFontSize(for: .regular)) + panel.contentView?.addSubview(messageTextField) - super.init(window: panel) + super.init(window: panel) - panel.clickDelegate = self - } + panel.clickDelegate = self + } - @available(*, unavailable) - required init?(coder _: NSCoder) { - fatalError("init(coder:) has not been implemented") - } + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } - private func show() { - func setStartLocation() { - if NotifierController.instanceCount == 0 { - return - } - let lastLocation = NotifierController.lastLocation - let screenRect = NSScreen.main?.visibleFrame ?? NSRect.zero - var windowRect = window?.frame ?? NSRect.zero - windowRect.origin.x = lastLocation.x - windowRect.origin.y = lastLocation.y - 10 - windowRect.height + private func show() { + func setStartLocation() { + if NotifierController.instanceCount == 0 { + return + } + let lastLocation = NotifierController.lastLocation + let screenRect = NSScreen.main?.visibleFrame ?? NSRect.zero + var windowRect = window?.frame ?? NSRect.zero + windowRect.origin.x = lastLocation.x + windowRect.origin.y = lastLocation.y - 10 - windowRect.height - if windowRect.origin.y < screenRect.minY { - return - } + if windowRect.origin.y < screenRect.minY { + return + } - window?.setFrame(windowRect, display: true) - } + window?.setFrame(windowRect, display: true) + } - func moveIn() { - let afterRect = window?.frame ?? NSRect.zero - NotifierController.lastLocation = afterRect.origin - var beforeRect = afterRect - beforeRect.origin.y += 10 - window?.setFrame(beforeRect, display: true) - window?.orderFront(self) - window?.setFrame(afterRect, display: true, animate: true) - } + func moveIn() { + let afterRect = window?.frame ?? NSRect.zero + NotifierController.lastLocation = afterRect.origin + var beforeRect = afterRect + beforeRect.origin.y += 10 + window?.setFrame(beforeRect, display: true) + window?.orderFront(self) + window?.setFrame(afterRect, display: true, animate: true) + } - setStartLocation() - moveIn() - NotifierController.increaseInstanceCount() - waitTimer = Timer.scheduledTimer( - timeInterval: shouldStay ? 5 : 1, target: self, selector: #selector(fadeOut), - userInfo: nil, - repeats: false - ) - } + setStartLocation() + moveIn() + NotifierController.increaseInstanceCount() + waitTimer = Timer.scheduledTimer( + timeInterval: shouldStay ? 5 : 1, target: self, selector: #selector(fadeOut), + userInfo: nil, + repeats: false + ) + } - @objc private func doFadeOut(_: Timer) { - let opacity = window?.alphaValue ?? 0 - if opacity <= 0 { - close() - } else { - window?.alphaValue = opacity - 0.2 - } - } + @objc private func doFadeOut(_: Timer) { + let opacity = window?.alphaValue ?? 0 + if opacity <= 0 { + close() + } else { + window?.alphaValue = opacity - 0.2 + } + } - @objc private func fadeOut() { - waitTimer?.invalidate() - waitTimer = nil - NotifierController.decreaseInstanceCount() - fadeTimer = Timer.scheduledTimer( - timeInterval: 0.01, target: self, selector: #selector(doFadeOut(_:)), userInfo: nil, - repeats: true - ) - } + @objc private func fadeOut() { + waitTimer?.invalidate() + waitTimer = nil + NotifierController.decreaseInstanceCount() + fadeTimer = Timer.scheduledTimer( + timeInterval: 0.01, target: self, selector: #selector(doFadeOut(_:)), userInfo: nil, + repeats: true + ) + } - override public func close() { - waitTimer?.invalidate() - waitTimer = nil - fadeTimer?.invalidate() - fadeTimer = nil - super.close() - } + override public func close() { + waitTimer?.invalidate() + waitTimer = nil + fadeTimer?.invalidate() + fadeTimer = nil + super.close() + } - fileprivate func windowDidBecomeClicked(_: NotifierWindow) { - fadeOut() - } + fileprivate func windowDidBecomeClicked(_: NotifierWindow) { + fadeOut() + } } diff --git a/Source/UI/PrefUI/ctlPrefUI.swift b/Source/UI/PrefUI/ctlPrefUI.swift index e1d59bdc..cd079333 100644 --- a/Source/UI/PrefUI/ctlPrefUI.swift +++ b/Source/UI/PrefUI/ctlPrefUI.swift @@ -26,50 +26,50 @@ import Cocoa @available(macOS 11.0, *) class ctlPrefUI { - lazy var controller = PreferencesWindowController( - panes: [ - Preferences.Pane( - identifier: Preferences.PaneIdentifier(rawValue: "General"), - title: NSLocalizedString("General", comment: ""), - toolbarIcon: NSImage( - systemSymbolName: "wrench.and.screwdriver.fill", accessibilityDescription: "General Preferences" - ) - ?? NSImage(named: NSImage.homeTemplateName)! - ) { - suiPrefPaneGeneral() - }, - Preferences.Pane( - identifier: Preferences.PaneIdentifier(rawValue: "Experiences"), - title: NSLocalizedString("Experience", comment: ""), - toolbarIcon: NSImage( - systemSymbolName: "person.fill.questionmark", accessibilityDescription: "Experiences Preferences" - ) - ?? NSImage(named: NSImage.listViewTemplateName)! - ) { - suiPrefPaneExperience() - }, - Preferences.Pane( - identifier: Preferences.PaneIdentifier(rawValue: "Dictionary"), - title: NSLocalizedString("Dictionary", comment: ""), - toolbarIcon: NSImage( - systemSymbolName: "character.book.closed.fill", accessibilityDescription: "Dictionary Preferences" - ) - ?? NSImage(named: NSImage.bookmarksTemplateName)! - ) { - suiPrefPaneDictionary() - }, - Preferences.Pane( - identifier: Preferences.PaneIdentifier(rawValue: "Keyboard"), - title: NSLocalizedString("Keyboard", comment: ""), - toolbarIcon: NSImage( - systemSymbolName: "keyboard.macwindow", accessibilityDescription: "Keyboard Preferences" - ) - ?? NSImage(named: NSImage.actionTemplateName)! - ) { - suiPrefPaneKeyboard() - }, - ], - style: .toolbarItems - ) - static let shared = ctlPrefUI() + lazy var controller = PreferencesWindowController( + panes: [ + Preferences.Pane( + identifier: Preferences.PaneIdentifier(rawValue: "General"), + title: NSLocalizedString("General", comment: ""), + toolbarIcon: NSImage( + systemSymbolName: "wrench.and.screwdriver.fill", accessibilityDescription: "General Preferences" + ) + ?? NSImage(named: NSImage.homeTemplateName)! + ) { + suiPrefPaneGeneral() + }, + Preferences.Pane( + identifier: Preferences.PaneIdentifier(rawValue: "Experiences"), + title: NSLocalizedString("Experience", comment: ""), + toolbarIcon: NSImage( + systemSymbolName: "person.fill.questionmark", accessibilityDescription: "Experiences Preferences" + ) + ?? NSImage(named: NSImage.listViewTemplateName)! + ) { + suiPrefPaneExperience() + }, + Preferences.Pane( + identifier: Preferences.PaneIdentifier(rawValue: "Dictionary"), + title: NSLocalizedString("Dictionary", comment: ""), + toolbarIcon: NSImage( + systemSymbolName: "character.book.closed.fill", accessibilityDescription: "Dictionary Preferences" + ) + ?? NSImage(named: NSImage.bookmarksTemplateName)! + ) { + suiPrefPaneDictionary() + }, + Preferences.Pane( + identifier: Preferences.PaneIdentifier(rawValue: "Keyboard"), + title: NSLocalizedString("Keyboard", comment: ""), + toolbarIcon: NSImage( + systemSymbolName: "keyboard.macwindow", accessibilityDescription: "Keyboard Preferences" + ) + ?? NSImage(named: NSImage.actionTemplateName)! + ) { + suiPrefPaneKeyboard() + }, + ], + style: .toolbarItems + ) + static let shared = ctlPrefUI() } diff --git a/Source/UI/PrefUI/suiPrefPaneDictionary.swift b/Source/UI/PrefUI/suiPrefPaneDictionary.swift index f42860df..4c3794d7 100644 --- a/Source/UI/PrefUI/suiPrefPaneDictionary.swift +++ b/Source/UI/PrefUI/suiPrefPaneDictionary.swift @@ -26,111 +26,111 @@ import SwiftUI @available(macOS 11.0, *) struct suiPrefPaneDictionary: View { - private var fdrDefault = mgrLangModel.dataFolderPath(isDefaultFolder: true) - @State private var tbxUserDataPathSpecified: String = - UserDefaults.standard.string(forKey: UserDef.kUserDataFolderSpecified) - ?? mgrLangModel.dataFolderPath(isDefaultFolder: true) - @State private var selAutoReloadUserData: Bool = UserDefaults.standard.bool( - forKey: UserDef.kShouldAutoReloadUserDataFiles) - @State private var selEnableCNS11643: Bool = UserDefaults.standard.bool(forKey: UserDef.kCNS11643Enabled) - @State private var selEnableSymbolInputSupport: Bool = UserDefaults.standard.bool( - forKey: UserDef.kSymbolInputEnabled) - private let contentWidth: Double = { - switch mgrPrefs.appleLanguages[0] { - case "ja": - return 520 - default: - if mgrPrefs.appleLanguages[0].contains("zh-Han") { - return 480 - } else { - return 550 - } - } - }() + private var fdrDefault = mgrLangModel.dataFolderPath(isDefaultFolder: true) + @State private var tbxUserDataPathSpecified: String = + UserDefaults.standard.string(forKey: UserDef.kUserDataFolderSpecified) + ?? mgrLangModel.dataFolderPath(isDefaultFolder: true) + @State private var selAutoReloadUserData: Bool = UserDefaults.standard.bool( + forKey: UserDef.kShouldAutoReloadUserDataFiles) + @State private var selEnableCNS11643: Bool = UserDefaults.standard.bool(forKey: UserDef.kCNS11643Enabled) + @State private var selEnableSymbolInputSupport: Bool = UserDefaults.standard.bool( + forKey: UserDef.kSymbolInputEnabled) + private let contentWidth: Double = { + switch mgrPrefs.appleLanguages[0] { + case "ja": + return 520 + default: + if mgrPrefs.appleLanguages[0].contains("zh-Han") { + return 480 + } else { + return 550 + } + } + }() - var body: some View { - Preferences.Container(contentWidth: contentWidth) { - Preferences.Section(title: "", bottomDivider: true) { - Text(LocalizedStringKey("Choose your desired user data folder path. Will be omitted if invalid.")) - HStack { - TextField(fdrDefault, text: $tbxUserDataPathSpecified).disabled(true) - .help(tbxUserDataPathSpecified) - Button { - IME.dlgOpenPath.title = NSLocalizedString( - "Choose your desired user data folder.", comment: "" - ) - IME.dlgOpenPath.showsResizeIndicator = true - IME.dlgOpenPath.showsHiddenFiles = true - IME.dlgOpenPath.canChooseFiles = false - IME.dlgOpenPath.canChooseDirectories = true + var body: some View { + Preferences.Container(contentWidth: contentWidth) { + Preferences.Section(title: "", bottomDivider: true) { + Text(LocalizedStringKey("Choose your desired user data folder path. Will be omitted if invalid.")) + HStack { + TextField(fdrDefault, text: $tbxUserDataPathSpecified).disabled(true) + .help(tbxUserDataPathSpecified) + Button { + IME.dlgOpenPath.title = NSLocalizedString( + "Choose your desired user data folder.", comment: "" + ) + IME.dlgOpenPath.showsResizeIndicator = true + IME.dlgOpenPath.showsHiddenFiles = true + IME.dlgOpenPath.canChooseFiles = false + IME.dlgOpenPath.canChooseDirectories = true - let bolPreviousFolderValidity = mgrLangModel.checkIfSpecifiedUserDataFolderValid( - NSString(string: mgrPrefs.userDataFolderSpecified).expandingTildeInPath) + let bolPreviousFolderValidity = mgrLangModel.checkIfSpecifiedUserDataFolderValid( + NSString(string: mgrPrefs.userDataFolderSpecified).expandingTildeInPath) - if let window = ctlPrefUI.shared.controller.window { - IME.dlgOpenPath.beginSheetModal(for: window) { result in - if result == NSApplication.ModalResponse.OK { - if IME.dlgOpenPath.url != nil { - // CommonDialog 讀入的路徑沒有結尾斜槓,這會導致檔案目錄合規性判定失準。 - // 所以要手動補回來。 - var newPath = IME.dlgOpenPath.url!.path - newPath.ensureTrailingSlash() - if mgrLangModel.checkIfSpecifiedUserDataFolderValid(newPath) { - mgrPrefs.userDataFolderSpecified = newPath - tbxUserDataPathSpecified = mgrPrefs.userDataFolderSpecified - IME.initLangModels(userOnly: true) - } else { - clsSFX.beep() - if !bolPreviousFolderValidity { - mgrPrefs.resetSpecifiedUserDataFolder() - } - return - } - } - } else { - if !bolPreviousFolderValidity { - mgrPrefs.resetSpecifiedUserDataFolder() - } - return - } - } - } // End If self.window != nil - } label: { - Text("...") - } - Button { - mgrPrefs.resetSpecifiedUserDataFolder() - tbxUserDataPathSpecified = "" - } label: { - Text("↻") - } - } - Toggle( - LocalizedStringKey("Automatically reload user data files if changes detected"), - isOn: $selAutoReloadUserData - ).controlSize(.small).onChange(of: selAutoReloadUserData) { value in - mgrPrefs.shouldAutoReloadUserDataFiles = value - } - Divider() - Toggle(LocalizedStringKey("Enable CNS11643 Support (2022-01-27)"), isOn: $selEnableCNS11643) - .onChange(of: selEnableCNS11643) { value in - mgrPrefs.cns11643Enabled = value - } - Toggle( - LocalizedStringKey("Enable symbol input support (incl. certain emoji symbols)"), - isOn: $selEnableSymbolInputSupport - ) - .onChange(of: selEnableSymbolInputSupport) { value in - mgrPrefs.symbolInputEnabled = value - } - } - } - } + if let window = ctlPrefUI.shared.controller.window { + IME.dlgOpenPath.beginSheetModal(for: window) { result in + if result == NSApplication.ModalResponse.OK { + if IME.dlgOpenPath.url != nil { + // CommonDialog 讀入的路徑沒有結尾斜槓,這會導致檔案目錄合規性判定失準。 + // 所以要手動補回來。 + var newPath = IME.dlgOpenPath.url!.path + newPath.ensureTrailingSlash() + if mgrLangModel.checkIfSpecifiedUserDataFolderValid(newPath) { + mgrPrefs.userDataFolderSpecified = newPath + tbxUserDataPathSpecified = mgrPrefs.userDataFolderSpecified + IME.initLangModels(userOnly: true) + } else { + clsSFX.beep() + if !bolPreviousFolderValidity { + mgrPrefs.resetSpecifiedUserDataFolder() + } + return + } + } + } else { + if !bolPreviousFolderValidity { + mgrPrefs.resetSpecifiedUserDataFolder() + } + return + } + } + } // End If self.window != nil + } label: { + Text("...") + } + Button { + mgrPrefs.resetSpecifiedUserDataFolder() + tbxUserDataPathSpecified = "" + } label: { + Text("↻") + } + } + Toggle( + LocalizedStringKey("Automatically reload user data files if changes detected"), + isOn: $selAutoReloadUserData + ).controlSize(.small).onChange(of: selAutoReloadUserData) { value in + mgrPrefs.shouldAutoReloadUserDataFiles = value + } + Divider() + Toggle(LocalizedStringKey("Enable CNS11643 Support (2022-01-27)"), isOn: $selEnableCNS11643) + .onChange(of: selEnableCNS11643) { value in + mgrPrefs.cns11643Enabled = value + } + Toggle( + LocalizedStringKey("Enable symbol input support (incl. certain emoji symbols)"), + isOn: $selEnableSymbolInputSupport + ) + .onChange(of: selEnableSymbolInputSupport) { value in + mgrPrefs.symbolInputEnabled = value + } + } + } + } } @available(macOS 11.0, *) struct suiPrefPaneDictionary_Previews: PreviewProvider { - static var previews: some View { - suiPrefPaneDictionary() - } + static var previews: some View { + suiPrefPaneDictionary() + } } diff --git a/Source/UI/PrefUI/suiPrefPaneExperience.swift b/Source/UI/PrefUI/suiPrefPaneExperience.swift index 02a495fb..076feea6 100644 --- a/Source/UI/PrefUI/suiPrefPaneExperience.swift +++ b/Source/UI/PrefUI/suiPrefPaneExperience.swift @@ -27,140 +27,140 @@ import SwiftUI @available(macOS 11.0, *) struct suiPrefPaneExperience: View { - @State private var selSelectionKeysList = mgrPrefs.suggestedCandidateKeys - @State private var selSelectionKeys = - (UserDefaults.standard.string(forKey: UserDef.kCandidateKeys) ?? mgrPrefs.defaultCandidateKeys) as String - @State private var selCursorPosition = - UserDefaults.standard.bool( - forKey: UserDef.kSetRearCursorMode) ? 1 : 0 - @State private var selPushCursorAfterSelection = UserDefaults.standard.bool( - forKey: UserDef.kMoveCursorAfterSelectingCandidate) - @State private var selKeyBehaviorShiftTab = - UserDefaults.standard.bool(forKey: UserDef.kSpecifyShiftTabKeyBehavior) ? 1 : 0 - @State private var selKeyBehaviorShiftSpace = - UserDefaults.standard.bool( - forKey: UserDef.kSpecifyShiftSpaceKeyBehavior) ? 1 : 0 - @State private var selKeyBehaviorSpaceForCallingCandidate = UserDefaults.standard.bool( - forKey: UserDef.kChooseCandidateUsingSpace) - @State private var selKeyBehaviorESCForClearingTheBuffer = UserDefaults.standard.bool( - forKey: UserDef.kEscToCleanInputBuffer) - @State private var selEnableSCPCTypingMode = UserDefaults.standard.bool(forKey: UserDef.kUseSCPCTypingMode) - private let contentWidth: Double = { - switch mgrPrefs.appleLanguages[0] { - case "ja": - return 520 - default: - if mgrPrefs.appleLanguages[0].contains("zh-Han") { - return 480 - } else { - return 550 - } - } - }() + @State private var selSelectionKeysList = mgrPrefs.suggestedCandidateKeys + @State private var selSelectionKeys = + (UserDefaults.standard.string(forKey: UserDef.kCandidateKeys) ?? mgrPrefs.defaultCandidateKeys) as String + @State private var selCursorPosition = + UserDefaults.standard.bool( + forKey: UserDef.kSetRearCursorMode) ? 1 : 0 + @State private var selPushCursorAfterSelection = UserDefaults.standard.bool( + forKey: UserDef.kMoveCursorAfterSelectingCandidate) + @State private var selKeyBehaviorShiftTab = + UserDefaults.standard.bool(forKey: UserDef.kSpecifyShiftTabKeyBehavior) ? 1 : 0 + @State private var selKeyBehaviorShiftSpace = + UserDefaults.standard.bool( + forKey: UserDef.kSpecifyShiftSpaceKeyBehavior) ? 1 : 0 + @State private var selKeyBehaviorSpaceForCallingCandidate = UserDefaults.standard.bool( + forKey: UserDef.kChooseCandidateUsingSpace) + @State private var selKeyBehaviorESCForClearingTheBuffer = UserDefaults.standard.bool( + forKey: UserDef.kEscToCleanInputBuffer) + @State private var selEnableSCPCTypingMode = UserDefaults.standard.bool(forKey: UserDef.kUseSCPCTypingMode) + private let contentWidth: Double = { + switch mgrPrefs.appleLanguages[0] { + case "ja": + return 520 + default: + if mgrPrefs.appleLanguages[0].contains("zh-Han") { + return 480 + } else { + return 550 + } + } + }() - var body: some View { - Preferences.Container(contentWidth: contentWidth) { - Preferences.Section(bottomDivider: true, label: { Text(LocalizedStringKey("Selection Keys:")) }) { - ComboBox(items: mgrPrefs.suggestedCandidateKeys, text: $selSelectionKeys).frame(width: 180).onChange( - of: selSelectionKeys - ) { value in - let keys: String = (value.trimmingCharacters(in: .whitespacesAndNewlines) as String).charDeDuplicate - do { - try mgrPrefs.validate(candidateKeys: keys) - mgrPrefs.candidateKeys = keys - selSelectionKeys = mgrPrefs.candidateKeys - } catch mgrPrefs.CandidateKeyError.empty { - selSelectionKeys = mgrPrefs.candidateKeys - } catch { - if let window = ctlPrefUI.shared.controller.window { - let alert = NSAlert(error: error) - alert.beginSheetModal(for: window) { _ in - selSelectionKeys = mgrPrefs.candidateKeys - } - clsSFX.beep() - } - } - } - Text( - LocalizedStringKey( - "Choose or hit Enter to confim your prefered keys for selecting candidates." - ) - ) - .preferenceDescription() - } - Preferences.Section(bottomDivider: true, label: { Text(LocalizedStringKey("Cursor Selection:")) }) { - Picker("", selection: $selCursorPosition) { - Text(LocalizedStringKey("in front of the phrase (like macOS built-in Zhuyin IME)")).tag(0) - Text(LocalizedStringKey("at anyplace else (like Windows Yahoo KeyKey)")).tag(1) - }.onChange(of: selCursorPosition) { value in - mgrPrefs.setRearCursorMode = (value == 1) ? true : false - } - .labelsHidden() - .pickerStyle(RadioGroupPickerStyle()) - Text(LocalizedStringKey("Choose the cursor position where you want to list possible candidates.")) - .preferenceDescription() - Toggle( - LocalizedStringKey("Push the cursor in front of the phrase after selection"), - isOn: $selPushCursorAfterSelection - ).onChange(of: selPushCursorAfterSelection) { value in - mgrPrefs.moveCursorAfterSelectingCandidate = value - }.controlSize(.small) - } - Preferences.Section(title: "(Shift+)Tab:", bottomDivider: true) { - Picker("", selection: $selKeyBehaviorShiftTab) { - Text(LocalizedStringKey("for cycling candidates")).tag(0) - Text(LocalizedStringKey("for cycling pages")).tag(1) - }.onChange(of: selKeyBehaviorShiftTab) { value in - mgrPrefs.specifyShiftTabKeyBehavior = (value == 1) ? true : false - } - .labelsHidden() - .horizontalRadioGroupLayout() - .pickerStyle(RadioGroupPickerStyle()) - Text(LocalizedStringKey("Choose the behavior of (Shift+)Tab key in the candidate window.")) - .preferenceDescription() - } - Preferences.Section(bottomDivider: true, label: { Text(LocalizedStringKey("(Shift+)Space:")) }) { - Picker("", selection: $selKeyBehaviorShiftSpace) { - Text(LocalizedStringKey("Space to +cycle candidates, Shift+Space to +cycle pages")).tag(0) - Text(LocalizedStringKey("Space to +cycle pages, Shift+Space to +cycle candidates")).tag(1) - }.onChange(of: selKeyBehaviorShiftSpace) { value in - mgrPrefs.specifyShiftSpaceKeyBehavior = (value == 1) ? true : false - } - .labelsHidden() - .pickerStyle(RadioGroupPickerStyle()) - Text(LocalizedStringKey("Choose the behavior of (Shift+)Space key in the candidate window.")) - .preferenceDescription() - } - Preferences.Section(bottomDivider: true, label: { Text(LocalizedStringKey("Space & ESC Key:")) }) { - Toggle( - LocalizedStringKey("Enable Space key for calling candidate window"), - isOn: $selKeyBehaviorSpaceForCallingCandidate - ).onChange(of: selKeyBehaviorSpaceForCallingCandidate) { value in - mgrPrefs.chooseCandidateUsingSpace = value - } - Toggle( - LocalizedStringKey("Use ESC key to clear the entire input buffer"), - isOn: $selKeyBehaviorESCForClearingTheBuffer - ).onChange(of: selKeyBehaviorESCForClearingTheBuffer) { value in - mgrPrefs.escToCleanInputBuffer = value - } - } - Preferences.Section(label: { Text(LocalizedStringKey("Typing Style:")) }) { - Toggle( - LocalizedStringKey("Emulating select-candidate-per-character mode"), isOn: $selEnableSCPCTypingMode - ).onChange(of: selEnableSCPCTypingMode) { value in - mgrPrefs.useSCPCTypingMode = value - } - Text(LocalizedStringKey("An accomodation for elder computer users.")) - .preferenceDescription() - } - } - } + var body: some View { + Preferences.Container(contentWidth: contentWidth) { + Preferences.Section(bottomDivider: true, label: { Text(LocalizedStringKey("Selection Keys:")) }) { + ComboBox(items: mgrPrefs.suggestedCandidateKeys, text: $selSelectionKeys).frame(width: 180).onChange( + of: selSelectionKeys + ) { value in + let keys: String = (value.trimmingCharacters(in: .whitespacesAndNewlines) as String).charDeDuplicate + do { + try mgrPrefs.validate(candidateKeys: keys) + mgrPrefs.candidateKeys = keys + selSelectionKeys = mgrPrefs.candidateKeys + } catch mgrPrefs.CandidateKeyError.empty { + selSelectionKeys = mgrPrefs.candidateKeys + } catch { + if let window = ctlPrefUI.shared.controller.window { + let alert = NSAlert(error: error) + alert.beginSheetModal(for: window) { _ in + selSelectionKeys = mgrPrefs.candidateKeys + } + clsSFX.beep() + } + } + } + Text( + LocalizedStringKey( + "Choose or hit Enter to confim your prefered keys for selecting candidates." + ) + ) + .preferenceDescription() + } + Preferences.Section(bottomDivider: true, label: { Text(LocalizedStringKey("Cursor Selection:")) }) { + Picker("", selection: $selCursorPosition) { + Text(LocalizedStringKey("in front of the phrase (like macOS built-in Zhuyin IME)")).tag(0) + Text(LocalizedStringKey("at anyplace else (like Windows Yahoo KeyKey)")).tag(1) + }.onChange(of: selCursorPosition) { value in + mgrPrefs.setRearCursorMode = (value == 1) ? true : false + } + .labelsHidden() + .pickerStyle(RadioGroupPickerStyle()) + Text(LocalizedStringKey("Choose the cursor position where you want to list possible candidates.")) + .preferenceDescription() + Toggle( + LocalizedStringKey("Push the cursor in front of the phrase after selection"), + isOn: $selPushCursorAfterSelection + ).onChange(of: selPushCursorAfterSelection) { value in + mgrPrefs.moveCursorAfterSelectingCandidate = value + }.controlSize(.small) + } + Preferences.Section(title: "(Shift+)Tab:", bottomDivider: true) { + Picker("", selection: $selKeyBehaviorShiftTab) { + Text(LocalizedStringKey("for cycling candidates")).tag(0) + Text(LocalizedStringKey("for cycling pages")).tag(1) + }.onChange(of: selKeyBehaviorShiftTab) { value in + mgrPrefs.specifyShiftTabKeyBehavior = (value == 1) ? true : false + } + .labelsHidden() + .horizontalRadioGroupLayout() + .pickerStyle(RadioGroupPickerStyle()) + Text(LocalizedStringKey("Choose the behavior of (Shift+)Tab key in the candidate window.")) + .preferenceDescription() + } + Preferences.Section(bottomDivider: true, label: { Text(LocalizedStringKey("(Shift+)Space:")) }) { + Picker("", selection: $selKeyBehaviorShiftSpace) { + Text(LocalizedStringKey("Space to +cycle candidates, Shift+Space to +cycle pages")).tag(0) + Text(LocalizedStringKey("Space to +cycle pages, Shift+Space to +cycle candidates")).tag(1) + }.onChange(of: selKeyBehaviorShiftSpace) { value in + mgrPrefs.specifyShiftSpaceKeyBehavior = (value == 1) ? true : false + } + .labelsHidden() + .pickerStyle(RadioGroupPickerStyle()) + Text(LocalizedStringKey("Choose the behavior of (Shift+)Space key in the candidate window.")) + .preferenceDescription() + } + Preferences.Section(bottomDivider: true, label: { Text(LocalizedStringKey("Space & ESC Key:")) }) { + Toggle( + LocalizedStringKey("Enable Space key for calling candidate window"), + isOn: $selKeyBehaviorSpaceForCallingCandidate + ).onChange(of: selKeyBehaviorSpaceForCallingCandidate) { value in + mgrPrefs.chooseCandidateUsingSpace = value + } + Toggle( + LocalizedStringKey("Use ESC key to clear the entire input buffer"), + isOn: $selKeyBehaviorESCForClearingTheBuffer + ).onChange(of: selKeyBehaviorESCForClearingTheBuffer) { value in + mgrPrefs.escToCleanInputBuffer = value + } + } + Preferences.Section(label: { Text(LocalizedStringKey("Typing Style:")) }) { + Toggle( + LocalizedStringKey("Emulating select-candidate-per-character mode"), isOn: $selEnableSCPCTypingMode + ).onChange(of: selEnableSCPCTypingMode) { value in + mgrPrefs.useSCPCTypingMode = value + } + Text(LocalizedStringKey("An accomodation for elder computer users.")) + .preferenceDescription() + } + } + } } @available(macOS 11.0, *) struct suiPrefPaneExperience_Previews: PreviewProvider { - static var previews: some View { - suiPrefPaneExperience() - } + static var previews: some View { + suiPrefPaneExperience() + } } diff --git a/Source/UI/PrefUI/suiPrefPaneGeneral.swift b/Source/UI/PrefUI/suiPrefPaneGeneral.swift index 2615d8a3..83497d62 100644 --- a/Source/UI/PrefUI/suiPrefPaneGeneral.swift +++ b/Source/UI/PrefUI/suiPrefPaneGeneral.swift @@ -27,152 +27,152 @@ import SwiftUI @available(macOS 11.0, *) struct suiPrefPaneGeneral: View { - @State private var selCandidateUIFontSize = UserDefaults.standard.integer(forKey: UserDef.kCandidateListTextSize) - @State private var selUILanguage: [String] = - IME.arrSupportedLocales.contains( - ((UserDefaults.standard.object(forKey: UserDef.kAppleLanguages) == nil) - ? ["auto"] : UserDefaults.standard.array(forKey: UserDef.kAppleLanguages) as? [String] ?? ["auto"])[0]) - ? ((UserDefaults.standard.object(forKey: UserDef.kAppleLanguages) == nil) - ? ["auto"] : UserDefaults.standard.array(forKey: UserDef.kAppleLanguages) as? [String] ?? ["auto"]) - : ["auto"] - @State private var selEnableHorizontalCandidateLayout = UserDefaults.standard.bool( - forKey: UserDef.kUseHorizontalCandidateList) - @State private var selShowPageButtonsInCandidateUI = UserDefaults.standard.bool( - forKey: UserDef.kShowPageButtonsInCandidateWindow) - @State private var selEnableKanjiConvToKangXi = UserDefaults.standard.bool( - forKey: UserDef.kChineseConversionEnabled) - @State private var selEnableKanjiConvToJIS = UserDefaults.standard.bool( - forKey: UserDef.kShiftJISShinjitaiOutputEnabled) - @State private var selEnableFartSuppressor = UserDefaults.standard.bool(forKey: UserDef.kShouldNotFartInLieuOfBeep) - @State private var selEnableAutoUpdateCheck = UserDefaults.standard.bool(forKey: UserDef.kCheckUpdateAutomatically) - @State private var selEnableDebugMode = UserDefaults.standard.bool(forKey: UserDef.kIsDebugModeEnabled) - private let contentWidth: Double = { - switch mgrPrefs.appleLanguages[0] { - case "ja": - return 465 - default: - if mgrPrefs.appleLanguages[0].contains("zh-Han") { - return 450 - } else { - return 550 - } - } - }() + @State private var selCandidateUIFontSize = UserDefaults.standard.integer(forKey: UserDef.kCandidateListTextSize) + @State private var selUILanguage: [String] = + IME.arrSupportedLocales.contains( + ((UserDefaults.standard.object(forKey: UserDef.kAppleLanguages) == nil) + ? ["auto"] : UserDefaults.standard.array(forKey: UserDef.kAppleLanguages) as? [String] ?? ["auto"])[0]) + ? ((UserDefaults.standard.object(forKey: UserDef.kAppleLanguages) == nil) + ? ["auto"] : UserDefaults.standard.array(forKey: UserDef.kAppleLanguages) as? [String] ?? ["auto"]) + : ["auto"] + @State private var selEnableHorizontalCandidateLayout = UserDefaults.standard.bool( + forKey: UserDef.kUseHorizontalCandidateList) + @State private var selShowPageButtonsInCandidateUI = UserDefaults.standard.bool( + forKey: UserDef.kShowPageButtonsInCandidateWindow) + @State private var selEnableKanjiConvToKangXi = UserDefaults.standard.bool( + forKey: UserDef.kChineseConversionEnabled) + @State private var selEnableKanjiConvToJIS = UserDefaults.standard.bool( + forKey: UserDef.kShiftJISShinjitaiOutputEnabled) + @State private var selEnableFartSuppressor = UserDefaults.standard.bool(forKey: UserDef.kShouldNotFartInLieuOfBeep) + @State private var selEnableAutoUpdateCheck = UserDefaults.standard.bool(forKey: UserDef.kCheckUpdateAutomatically) + @State private var selEnableDebugMode = UserDefaults.standard.bool(forKey: UserDef.kIsDebugModeEnabled) + private let contentWidth: Double = { + switch mgrPrefs.appleLanguages[0] { + case "ja": + return 465 + default: + if mgrPrefs.appleLanguages[0].contains("zh-Han") { + return 450 + } else { + return 550 + } + } + }() - var body: some View { - Preferences.Container(contentWidth: contentWidth) { - Preferences.Section(bottomDivider: false, label: { Text(LocalizedStringKey("Candidate Size:")) }) { - Picker("", selection: $selCandidateUIFontSize) { - Text("12").tag(12) - Text("14").tag(14) - Text("16").tag(16) - Text("18").tag(18) - Text("24").tag(24) - Text("32").tag(32) - Text("64").tag(64) - Text("96").tag(96) - }.onChange(of: selCandidateUIFontSize) { value in - mgrPrefs.candidateListTextSize = CGFloat(value) - } - .labelsHidden() - .frame(width: 120.0) - Text(LocalizedStringKey("Choose candidate font size for better visual clarity.")) - .preferenceDescription() - } - Preferences.Section(bottomDivider: false, label: { Text(LocalizedStringKey("UI Language:")) }) { - Picker(LocalizedStringKey("Follow OS settings"), selection: $selUILanguage) { - Text(LocalizedStringKey("Follow OS settings")).tag(["auto"]) - Text(LocalizedStringKey("Simplified Chinese")).tag(["zh-Hans"]) - Text(LocalizedStringKey("Traditional Chinese")).tag(["zh-Hant"]) - Text(LocalizedStringKey("Japanese")).tag(["ja"]) - Text(LocalizedStringKey("English")).tag(["en"]) - }.onChange(of: selUILanguage) { value in - IME.prtDebugIntel(value[0]) - if selUILanguage == mgrPrefs.appleLanguages - || (selUILanguage[0] == "auto" - && UserDefaults.standard.object(forKey: UserDef.kAppleLanguages) == nil) - { - return - } - if selUILanguage[0] != "auto" { - mgrPrefs.appleLanguages = value - } else { - UserDefaults.standard.removeObject(forKey: UserDef.kAppleLanguages) - } - NSLog("vChewing App self-terminated due to UI language change.") - NSApplication.shared.terminate(nil) - } - .labelsHidden() - .frame(width: 180.0) + var body: some View { + Preferences.Container(contentWidth: contentWidth) { + Preferences.Section(bottomDivider: false, label: { Text(LocalizedStringKey("Candidate Size:")) }) { + Picker("", selection: $selCandidateUIFontSize) { + Text("12").tag(12) + Text("14").tag(14) + Text("16").tag(16) + Text("18").tag(18) + Text("24").tag(24) + Text("32").tag(32) + Text("64").tag(64) + Text("96").tag(96) + }.onChange(of: selCandidateUIFontSize) { value in + mgrPrefs.candidateListTextSize = CGFloat(value) + } + .labelsHidden() + .frame(width: 120.0) + Text(LocalizedStringKey("Choose candidate font size for better visual clarity.")) + .preferenceDescription() + } + Preferences.Section(bottomDivider: false, label: { Text(LocalizedStringKey("UI Language:")) }) { + Picker(LocalizedStringKey("Follow OS settings"), selection: $selUILanguage) { + Text(LocalizedStringKey("Follow OS settings")).tag(["auto"]) + Text(LocalizedStringKey("Simplified Chinese")).tag(["zh-Hans"]) + Text(LocalizedStringKey("Traditional Chinese")).tag(["zh-Hant"]) + Text(LocalizedStringKey("Japanese")).tag(["ja"]) + Text(LocalizedStringKey("English")).tag(["en"]) + }.onChange(of: selUILanguage) { value in + IME.prtDebugIntel(value[0]) + if selUILanguage == mgrPrefs.appleLanguages + || (selUILanguage[0] == "auto" + && UserDefaults.standard.object(forKey: UserDef.kAppleLanguages) == nil) + { + return + } + if selUILanguage[0] != "auto" { + mgrPrefs.appleLanguages = value + } else { + UserDefaults.standard.removeObject(forKey: UserDef.kAppleLanguages) + } + NSLog("vChewing App self-terminated due to UI language change.") + NSApplication.shared.terminate(nil) + } + .labelsHidden() + .frame(width: 180.0) - Text(LocalizedStringKey("Change user interface language (will reboot the IME).")) - .preferenceDescription() - } - Preferences.Section(bottomDivider: true, label: { Text(LocalizedStringKey("Candidate Layout:")) }) { - Picker("", selection: $selEnableHorizontalCandidateLayout) { - Text(LocalizedStringKey("Vertical")).tag(false) - Text(LocalizedStringKey("Horizontal")).tag(true) - }.onChange(of: selEnableHorizontalCandidateLayout) { value in - mgrPrefs.useHorizontalCandidateList = value - } - .labelsHidden() - .horizontalRadioGroupLayout() - .pickerStyle(RadioGroupPickerStyle()) - Text(LocalizedStringKey("Choose your preferred layout of the candidate window.")) - .preferenceDescription() - Toggle( - LocalizedStringKey("Show page buttons in candidate window"), isOn: $selShowPageButtonsInCandidateUI - ).controlSize(.small) - } - Preferences.Section(bottomDivider: true, label: { Text(LocalizedStringKey("Output Settings:")) }) { - Toggle( - LocalizedStringKey("Auto-convert traditional Chinese glyphs to KangXi characters"), - isOn: $selEnableKanjiConvToKangXi - ).onChange(of: selEnableKanjiConvToKangXi) { value in - mgrPrefs.chineseConversionEnabled = value - selEnableKanjiConvToKangXi = value - if value { - mgrPrefs.shiftJISShinjitaiOutputEnabled = !value - selEnableKanjiConvToJIS = !value - } - } - Toggle( - LocalizedStringKey("Auto-convert traditional Chinese glyphs to JIS Shinjitai characters"), - isOn: $selEnableKanjiConvToJIS - ).onChange(of: selEnableKanjiConvToJIS) { value in - mgrPrefs.shiftJISShinjitaiOutputEnabled = value - selEnableKanjiConvToJIS = value - if value { - mgrPrefs.chineseConversionEnabled = !value - selEnableKanjiConvToKangXi = !value - } - } - Toggle( - LocalizedStringKey("Stop farting (when typed phonetic combination is invalid, etc.)"), - isOn: $selEnableFartSuppressor - ).onChange(of: selEnableFartSuppressor) { value in - mgrPrefs.shouldNotFartInLieuOfBeep = value - clsSFX.beep() - } - } - Preferences.Section(label: { Text(LocalizedStringKey("Misc Settings:")).controlSize(.small) }) { - Toggle(LocalizedStringKey("Check for updates automatically"), isOn: $selEnableAutoUpdateCheck) - .onChange(of: selEnableAutoUpdateCheck) { value in - mgrPrefs.checkUpdateAutomatically = value - } - .controlSize(.small) - Toggle(LocalizedStringKey("Debug Mode"), isOn: $selEnableDebugMode).controlSize(.small) - .onChange(of: selEnableDebugMode) { value in - mgrPrefs.isDebugModeEnabled = value - } - } - } - } + Text(LocalizedStringKey("Change user interface language (will reboot the IME).")) + .preferenceDescription() + } + Preferences.Section(bottomDivider: true, label: { Text(LocalizedStringKey("Candidate Layout:")) }) { + Picker("", selection: $selEnableHorizontalCandidateLayout) { + Text(LocalizedStringKey("Vertical")).tag(false) + Text(LocalizedStringKey("Horizontal")).tag(true) + }.onChange(of: selEnableHorizontalCandidateLayout) { value in + mgrPrefs.useHorizontalCandidateList = value + } + .labelsHidden() + .horizontalRadioGroupLayout() + .pickerStyle(RadioGroupPickerStyle()) + Text(LocalizedStringKey("Choose your preferred layout of the candidate window.")) + .preferenceDescription() + Toggle( + LocalizedStringKey("Show page buttons in candidate window"), isOn: $selShowPageButtonsInCandidateUI + ).controlSize(.small) + } + Preferences.Section(bottomDivider: true, label: { Text(LocalizedStringKey("Output Settings:")) }) { + Toggle( + LocalizedStringKey("Auto-convert traditional Chinese glyphs to KangXi characters"), + isOn: $selEnableKanjiConvToKangXi + ).onChange(of: selEnableKanjiConvToKangXi) { value in + mgrPrefs.chineseConversionEnabled = value + selEnableKanjiConvToKangXi = value + if value { + mgrPrefs.shiftJISShinjitaiOutputEnabled = !value + selEnableKanjiConvToJIS = !value + } + } + Toggle( + LocalizedStringKey("Auto-convert traditional Chinese glyphs to JIS Shinjitai characters"), + isOn: $selEnableKanjiConvToJIS + ).onChange(of: selEnableKanjiConvToJIS) { value in + mgrPrefs.shiftJISShinjitaiOutputEnabled = value + selEnableKanjiConvToJIS = value + if value { + mgrPrefs.chineseConversionEnabled = !value + selEnableKanjiConvToKangXi = !value + } + } + Toggle( + LocalizedStringKey("Stop farting (when typed phonetic combination is invalid, etc.)"), + isOn: $selEnableFartSuppressor + ).onChange(of: selEnableFartSuppressor) { value in + mgrPrefs.shouldNotFartInLieuOfBeep = value + clsSFX.beep() + } + } + Preferences.Section(label: { Text(LocalizedStringKey("Misc Settings:")).controlSize(.small) }) { + Toggle(LocalizedStringKey("Check for updates automatically"), isOn: $selEnableAutoUpdateCheck) + .onChange(of: selEnableAutoUpdateCheck) { value in + mgrPrefs.checkUpdateAutomatically = value + } + .controlSize(.small) + Toggle(LocalizedStringKey("Debug Mode"), isOn: $selEnableDebugMode).controlSize(.small) + .onChange(of: selEnableDebugMode) { value in + mgrPrefs.isDebugModeEnabled = value + } + } + } + } } @available(macOS 11.0, *) struct suiPrefPaneGeneral_Previews: PreviewProvider { - static var previews: some View { - suiPrefPaneGeneral() - } + static var previews: some View { + suiPrefPaneGeneral() + } } diff --git a/Source/UI/PrefUI/suiPrefPaneKeyboard.swift b/Source/UI/PrefUI/suiPrefPaneKeyboard.swift index 6c2ae23d..af59371f 100644 --- a/Source/UI/PrefUI/suiPrefPaneKeyboard.swift +++ b/Source/UI/PrefUI/suiPrefPaneKeyboard.swift @@ -26,84 +26,84 @@ import SwiftUI @available(macOS 11.0, *) struct suiPrefPaneKeyboard: View { - @State private var selMandarinParser = UserDefaults.standard.integer(forKey: UserDef.kMandarinParser) - @State private var selBasicKeyboardLayout: String = - UserDefaults.standard.string(forKey: UserDef.kBasicKeyboardLayout) ?? mgrPrefs.basicKeyboardLayout - private let contentWidth: Double = { - switch mgrPrefs.appleLanguages[0] { - case "ja": - return 520 - default: - if mgrPrefs.appleLanguages[0].contains("zh-Han") { - return 480 - } else { - return 550 - } - } - }() + @State private var selMandarinParser = UserDefaults.standard.integer(forKey: UserDef.kMandarinParser) + @State private var selBasicKeyboardLayout: String = + UserDefaults.standard.string(forKey: UserDef.kBasicKeyboardLayout) ?? mgrPrefs.basicKeyboardLayout + private let contentWidth: Double = { + switch mgrPrefs.appleLanguages[0] { + case "ja": + return 520 + default: + if mgrPrefs.appleLanguages[0].contains("zh-Han") { + return 480 + } else { + return 550 + } + } + }() - var body: some View { - Preferences.Container(contentWidth: contentWidth) { - Preferences.Section(label: { Text(LocalizedStringKey("Phonetic Parser:")) }) { - Picker("", selection: $selMandarinParser) { - Text(LocalizedStringKey("Dachen (Microsoft Standard / Wang / 01, etc.)")).tag(0) - Text(LocalizedStringKey("Eten Traditional")).tag(1) - Text(LocalizedStringKey("Eten 26")).tag(3) - Text(LocalizedStringKey("IBM")).tag(4) - Text(LocalizedStringKey("Hsu")).tag(2) - Text(LocalizedStringKey("MiTAC")).tag(5) - Text(LocalizedStringKey("Fake Seigyou")).tag(6) - Text(LocalizedStringKey("Hanyu Pinyin with Numeral Intonation")).tag(10) - }.onChange(of: selMandarinParser) { value in - mgrPrefs.mandarinParser = value - } - .labelsHidden() - .frame(width: 320.0) - Text(LocalizedStringKey("Choose the phonetic layout for Mandarin parser.")) - .preferenceDescription() - } - Preferences.Section(bottomDivider: true, label: { Text(LocalizedStringKey("Basic Keyboard Layout:")) }) { - HStack { - Picker("", selection: $selBasicKeyboardLayout) { - ForEach(0...(IME.arrEnumerateSystemKeyboardLayouts.count - 1), id: \.self) { id in - Text(IME.arrEnumerateSystemKeyboardLayouts[id].strName).tag( - IME.arrEnumerateSystemKeyboardLayouts[id].strValue) - }.id(UUID()) - }.onChange(of: selBasicKeyboardLayout) { value in - mgrPrefs.basicKeyboardLayout = value - } - .labelsHidden() - .frame(width: 240.0) - } - Text(LocalizedStringKey("Choose the macOS-level basic keyboard layout.")) - .preferenceDescription() - } - } - Divider() - Preferences.Container(contentWidth: contentWidth) { - Preferences.Section(title: "") { - VStack(alignment: .leading, spacing: 10) { - Text( - LocalizedStringKey( - "Non-QWERTY alphanumeral keyboard layouts are for Hanyu Pinyin parser only." - ) - ) - .preferenceDescription() - Text( - LocalizedStringKey( - "Apple Dynamic Bopomofo Basic Keyboard Layouts (Dachen & Eten Traditional) must match the Dachen parser in order to be functional." - ) - ) - .preferenceDescription() - } - } - } - } + var body: some View { + Preferences.Container(contentWidth: contentWidth) { + Preferences.Section(label: { Text(LocalizedStringKey("Phonetic Parser:")) }) { + Picker("", selection: $selMandarinParser) { + Text(LocalizedStringKey("Dachen (Microsoft Standard / Wang / 01, etc.)")).tag(0) + Text(LocalizedStringKey("Eten Traditional")).tag(1) + Text(LocalizedStringKey("Eten 26")).tag(3) + Text(LocalizedStringKey("IBM")).tag(4) + Text(LocalizedStringKey("Hsu")).tag(2) + Text(LocalizedStringKey("MiTAC")).tag(5) + Text(LocalizedStringKey("Fake Seigyou")).tag(6) + Text(LocalizedStringKey("Hanyu Pinyin with Numeral Intonation")).tag(10) + }.onChange(of: selMandarinParser) { value in + mgrPrefs.mandarinParser = value + } + .labelsHidden() + .frame(width: 320.0) + Text(LocalizedStringKey("Choose the phonetic layout for Mandarin parser.")) + .preferenceDescription() + } + Preferences.Section(bottomDivider: true, label: { Text(LocalizedStringKey("Basic Keyboard Layout:")) }) { + HStack { + Picker("", selection: $selBasicKeyboardLayout) { + ForEach(0...(IME.arrEnumerateSystemKeyboardLayouts.count - 1), id: \.self) { id in + Text(IME.arrEnumerateSystemKeyboardLayouts[id].strName).tag( + IME.arrEnumerateSystemKeyboardLayouts[id].strValue) + }.id(UUID()) + }.onChange(of: selBasicKeyboardLayout) { value in + mgrPrefs.basicKeyboardLayout = value + } + .labelsHidden() + .frame(width: 240.0) + } + Text(LocalizedStringKey("Choose the macOS-level basic keyboard layout.")) + .preferenceDescription() + } + } + Divider() + Preferences.Container(contentWidth: contentWidth) { + Preferences.Section(title: "") { + VStack(alignment: .leading, spacing: 10) { + Text( + LocalizedStringKey( + "Non-QWERTY alphanumeral keyboard layouts are for Hanyu Pinyin parser only." + ) + ) + .preferenceDescription() + Text( + LocalizedStringKey( + "Apple Dynamic Bopomofo Basic Keyboard Layouts (Dachen & Eten Traditional) must match the Dachen parser in order to be functional." + ) + ) + .preferenceDescription() + } + } + } + } } @available(macOS 11.0, *) struct suiPrefPaneKeyboard_Previews: PreviewProvider { - static var previews: some View { - suiPrefPaneKeyboard() - } + static var previews: some View { + suiPrefPaneKeyboard() + } } diff --git a/Source/UI/TooltipUI/TooltipController.swift b/Source/UI/TooltipUI/TooltipController.swift index 70ad4db9..626b3fa7 100644 --- a/Source/UI/TooltipUI/TooltipController.swift +++ b/Source/UI/TooltipUI/TooltipController.swift @@ -27,104 +27,103 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import Cocoa public class TooltipController: NSWindowController { - static var backgroundColor = NSColor.windowBackgroundColor - static var textColor = NSColor.windowBackgroundColor - private var messageTextField: NSTextField - private var tooltip: String = "" { - didSet { - messageTextField.stringValue = tooltip - adjustSize() - } - } + static var backgroundColor = NSColor.windowBackgroundColor + static var textColor = NSColor.windowBackgroundColor + private var messageTextField: NSTextField + private var tooltip: String = "" { + didSet { + messageTextField.stringValue = tooltip + adjustSize() + } + } - public init() { - let contentRect = NSRect(x: 128.0, y: 128.0, width: 300.0, height: 20.0) - let styleMask: NSWindow.StyleMask = [.borderless, .nonactivatingPanel] - let panel = NSPanel( - contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false - ) - panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel) + 1) - panel.hasShadow = true + public init() { + let contentRect = NSRect(x: 128.0, y: 128.0, width: 300.0, height: 20.0) + let styleMask: NSWindow.StyleMask = [.borderless, .nonactivatingPanel] + let panel = NSPanel( + contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false + ) + panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel) + 1) + panel.hasShadow = true - messageTextField = NSTextField() - messageTextField.isEditable = false - messageTextField.isSelectable = false - messageTextField.isBezeled = false - messageTextField.textColor = TooltipController.textColor - messageTextField.drawsBackground = true - messageTextField.backgroundColor = TooltipController.backgroundColor - messageTextField.font = .systemFont(ofSize: NSFont.systemFontSize(for: .small)) - panel.contentView?.addSubview(messageTextField) + messageTextField = NSTextField() + messageTextField.isEditable = false + messageTextField.isSelectable = false + messageTextField.isBezeled = false + messageTextField.textColor = TooltipController.textColor + messageTextField.drawsBackground = true + messageTextField.backgroundColor = TooltipController.backgroundColor + messageTextField.font = .systemFont(ofSize: NSFont.systemFontSize(for: .small)) + panel.contentView?.addSubview(messageTextField) - super.init(window: panel) - } + super.init(window: panel) + } - @available(*, unavailable) - public required init?(coder _: NSCoder) { - fatalError("init(coder:) has not been implemented") - } + @available(*, unavailable) + public required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } - @objc(showTooltip:atPoint:) - public func show(tooltip: String, at point: NSPoint) { - messageTextField.textColor = TooltipController.textColor - messageTextField.backgroundColor = TooltipController.backgroundColor - self.tooltip = tooltip - window?.orderFront(nil) - set(windowLocation: point) - } + public func show(tooltip: String, at point: NSPoint) { + messageTextField.textColor = TooltipController.textColor + messageTextField.backgroundColor = TooltipController.backgroundColor + self.tooltip = tooltip + window?.orderFront(nil) + set(windowLocation: point) + } - @objc - public func hide() { - window?.orderOut(nil) - } + @objc + public func hide() { + window?.orderOut(nil) + } - private func set(windowLocation windowTopLeftPoint: NSPoint) { - var adjustedPoint = windowTopLeftPoint - adjustedPoint.y -= 5 + private func set(windowLocation windowTopLeftPoint: NSPoint) { + var adjustedPoint = windowTopLeftPoint + adjustedPoint.y -= 5 - var screenFrame = NSScreen.main?.visibleFrame ?? NSRect.zero - for screen in NSScreen.screens { - let frame = screen.visibleFrame - if windowTopLeftPoint.x >= frame.minX, windowTopLeftPoint.x <= frame.maxX, - windowTopLeftPoint.y >= frame.minY, windowTopLeftPoint.y <= frame.maxY - { - screenFrame = frame - break - } - } + var screenFrame = NSScreen.main?.visibleFrame ?? NSRect.zero + for screen in NSScreen.screens { + let frame = screen.visibleFrame + if windowTopLeftPoint.x >= frame.minX, windowTopLeftPoint.x <= frame.maxX, + windowTopLeftPoint.y >= frame.minY, windowTopLeftPoint.y <= frame.maxY + { + screenFrame = frame + break + } + } - let windowSize = window?.frame.size ?? NSSize.zero + let windowSize = window?.frame.size ?? NSSize.zero - // bottom beneath the screen? - if adjustedPoint.y - windowSize.height < screenFrame.minY { - adjustedPoint.y = screenFrame.minY + windowSize.height - } + // bottom beneath the screen? + if adjustedPoint.y - windowSize.height < screenFrame.minY { + adjustedPoint.y = screenFrame.minY + windowSize.height + } - // top over the screen? - if adjustedPoint.y >= screenFrame.maxY { - adjustedPoint.y = screenFrame.maxY - 1.0 - } + // top over the screen? + if adjustedPoint.y >= screenFrame.maxY { + adjustedPoint.y = screenFrame.maxY - 1.0 + } - // right - if adjustedPoint.x + windowSize.width >= screenFrame.maxX { - adjustedPoint.x = screenFrame.maxX - windowSize.width - } + // right + if adjustedPoint.x + windowSize.width >= screenFrame.maxX { + adjustedPoint.x = screenFrame.maxX - windowSize.width + } - // left - if adjustedPoint.x < screenFrame.minX { - adjustedPoint.x = screenFrame.minX - } + // left + if adjustedPoint.x < screenFrame.minX { + adjustedPoint.x = screenFrame.minX + } - window?.setFrameTopLeftPoint(adjustedPoint) - } + window?.setFrameTopLeftPoint(adjustedPoint) + } - private func adjustSize() { - let attrString = messageTextField.attributedStringValue - var rect = attrString.boundingRect( - with: NSSize(width: 1600.0, height: 1600.0), options: .usesLineFragmentOrigin - ) - rect.size.width += 10 - messageTextField.frame = rect - window?.setFrame(rect, display: true) - } + private func adjustSize() { + let attrString = messageTextField.attributedStringValue + var rect = attrString.boundingRect( + with: NSSize(width: 1600.0, height: 1600.0), options: .usesLineFragmentOrigin + ) + rect.size.width += 10 + messageTextField.frame = rect + window?.setFrame(rect, display: true) + } } diff --git a/Source/WindowControllers/ctlAboutWindow.swift b/Source/WindowControllers/ctlAboutWindow.swift index 54ca1d16..f653e09d 100644 --- a/Source/WindowControllers/ctlAboutWindow.swift +++ b/Source/WindowControllers/ctlAboutWindow.swift @@ -26,40 +26,40 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import Cocoa -@objc(AboutWindow) class ctlAboutWindow: NSWindowController { - @IBOutlet var appVersionLabel: NSTextField! - @IBOutlet var appCopyrightLabel: NSTextField! - @IBOutlet var appEULAContent: NSTextView! +class ctlAboutWindow: NSWindowController { + @IBOutlet var appVersionLabel: NSTextField! + @IBOutlet var appCopyrightLabel: NSTextField! + @IBOutlet var appEULAContent: NSTextView! - override func windowDidLoad() { - super.windowDidLoad() + override func windowDidLoad() { + super.windowDidLoad() - window?.standardWindowButton(.closeButton)?.isHidden = true - window?.standardWindowButton(.miniaturizeButton)?.isHidden = true - window?.standardWindowButton(.zoomButton)?.isHidden = true - guard - let installingVersion = Bundle.main.infoDictionary?[kCFBundleVersionKey as String] - as? String, - let versionString = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String - else { - return - } - if let copyrightLabel = Bundle.main.localizedInfoDictionary?["NSHumanReadableCopyright"] - as? String - { - appCopyrightLabel.stringValue = copyrightLabel - } - if let eulaContent = Bundle.main.localizedInfoDictionary?["CFEULAContent"] as? String { - appEULAContent.string = eulaContent - } - appVersionLabel.stringValue = String( - format: "%@ Build %@", versionString, installingVersion - ) - } + window?.standardWindowButton(.closeButton)?.isHidden = true + window?.standardWindowButton(.miniaturizeButton)?.isHidden = true + window?.standardWindowButton(.zoomButton)?.isHidden = true + guard + let installingVersion = Bundle.main.infoDictionary?[kCFBundleVersionKey as String] + as? String, + let versionString = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String + else { + return + } + if let copyrightLabel = Bundle.main.localizedInfoDictionary?["NSHumanReadableCopyright"] + as? String + { + appCopyrightLabel.stringValue = copyrightLabel + } + if let eulaContent = Bundle.main.localizedInfoDictionary?["CFEULAContent"] as? String { + appEULAContent.string = eulaContent + } + appVersionLabel.stringValue = String( + format: "%@ Build %@", versionString, installingVersion + ) + } - @IBAction func btnWiki(_: NSButton) { - if let url = URL(string: "https://gitee.com/vchewing/vChewing-macOS/wikis") { - NSWorkspace.shared.open(url) - } - } + @IBAction func btnWiki(_: NSButton) { + if let url = URL(string: "https://gitee.com/vchewing/vChewing-macOS/wikis") { + NSWorkspace.shared.open(url) + } + } } diff --git a/Source/WindowControllers/ctlNonModalAlertWindow.swift b/Source/WindowControllers/ctlNonModalAlertWindow.swift index 7e654da7..f8e28ddc 100644 --- a/Source/WindowControllers/ctlNonModalAlertWindow.swift +++ b/Source/WindowControllers/ctlNonModalAlertWindow.swift @@ -26,105 +26,104 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import Cocoa -@objc protocol ctlNonModalAlertWindowDelegate: AnyObject { - func ctlNonModalAlertWindowDidConfirm(_ controller: ctlNonModalAlertWindow) - func ctlNonModalAlertWindowDidCancel(_ controller: ctlNonModalAlertWindow) +protocol ctlNonModalAlertWindowDelegate: AnyObject { + func ctlNonModalAlertWindowDidConfirm(_ controller: ctlNonModalAlertWindow) + func ctlNonModalAlertWindowDidCancel(_ controller: ctlNonModalAlertWindow) } class ctlNonModalAlertWindow: NSWindowController { - @objc(sharedInstance) - static let shared = ctlNonModalAlertWindow(windowNibName: "frmNonModalAlertWindow") + static let shared = ctlNonModalAlertWindow(windowNibName: "frmNonModalAlertWindow") - @IBOutlet var titleTextField: NSTextField! - @IBOutlet var contentTextField: NSTextField! - @IBOutlet var confirmButton: NSButton! - @IBOutlet var cancelButton: NSButton! - weak var delegate: ctlNonModalAlertWindowDelegate? + @IBOutlet var titleTextField: NSTextField! + @IBOutlet var contentTextField: NSTextField! + @IBOutlet var confirmButton: NSButton! + @IBOutlet var cancelButton: NSButton! + weak var delegate: ctlNonModalAlertWindowDelegate? - @objc func show( - title: String, content: String, confirmButtonTitle: String, cancelButtonTitle: String?, - cancelAsDefault: Bool, delegate: ctlNonModalAlertWindowDelegate? - ) { - if window?.isVisible == true { - self.delegate?.ctlNonModalAlertWindowDidCancel(self) - } + func show( + title: String, content: String, confirmButtonTitle: String, cancelButtonTitle: String?, + cancelAsDefault: Bool, delegate: ctlNonModalAlertWindowDelegate? + ) { + if window?.isVisible == true { + self.delegate?.ctlNonModalAlertWindowDidCancel(self) + } - self.delegate = delegate + self.delegate = delegate - var oldFrame = confirmButton.frame - confirmButton.title = confirmButtonTitle - confirmButton.sizeToFit() + var oldFrame = confirmButton.frame + confirmButton.title = confirmButtonTitle + confirmButton.sizeToFit() - var newFrame = confirmButton.frame - newFrame.size.width = max(90, newFrame.size.width + 10) - newFrame.origin.x += oldFrame.size.width - newFrame.size.width - confirmButton.frame = newFrame + var newFrame = confirmButton.frame + newFrame.size.width = max(90, newFrame.size.width + 10) + newFrame.origin.x += oldFrame.size.width - newFrame.size.width + confirmButton.frame = newFrame - if let cancelButtonTitle = cancelButtonTitle { - cancelButton.title = cancelButtonTitle - cancelButton.sizeToFit() - var adjustFrame = cancelButton.frame - adjustFrame.size.width = max(90, adjustFrame.size.width + 10) - adjustFrame.origin.x = newFrame.origin.x - adjustFrame.size.width - confirmButton.frame = adjustFrame - cancelButton.isHidden = false - } else { - cancelButton.isHidden = true - } + if let cancelButtonTitle = cancelButtonTitle { + cancelButton.title = cancelButtonTitle + cancelButton.sizeToFit() + var adjustFrame = cancelButton.frame + adjustFrame.size.width = max(90, adjustFrame.size.width + 10) + adjustFrame.origin.x = newFrame.origin.x - adjustFrame.size.width + confirmButton.frame = adjustFrame + cancelButton.isHidden = false + } else { + cancelButton.isHidden = true + } - cancelButton.nextKeyView = confirmButton - confirmButton.nextKeyView = cancelButton + cancelButton.nextKeyView = confirmButton + confirmButton.nextKeyView = cancelButton - if cancelButtonTitle != nil { - if cancelAsDefault { - window?.defaultButtonCell = cancelButton.cell as? NSButtonCell - } else { - cancelButton.keyEquivalent = " " - window?.defaultButtonCell = confirmButton.cell as? NSButtonCell - } - } else { - window?.defaultButtonCell = confirmButton.cell as? NSButtonCell - } + if cancelButtonTitle != nil { + if cancelAsDefault { + window?.defaultButtonCell = cancelButton.cell as? NSButtonCell + } else { + cancelButton.keyEquivalent = " " + window?.defaultButtonCell = confirmButton.cell as? NSButtonCell + } + } else { + window?.defaultButtonCell = confirmButton.cell as? NSButtonCell + } - titleTextField.stringValue = title + titleTextField.stringValue = title - oldFrame = contentTextField.frame - contentTextField.stringValue = content + oldFrame = contentTextField.frame + contentTextField.stringValue = content - var infiniteHeightFrame = oldFrame - infiniteHeightFrame.size.width -= 4.0 - infiniteHeightFrame.size.height = 10240 - newFrame = (content as NSString).boundingRect( - with: infiniteHeightFrame.size, options: [.usesLineFragmentOrigin], - attributes: [.font: contentTextField.font!] - ) - newFrame.size.width = max(newFrame.size.width, oldFrame.size.width) - newFrame.size.height += 4.0 - newFrame.origin = oldFrame.origin - newFrame.origin.y -= (newFrame.size.height - oldFrame.size.height) - contentTextField.frame = newFrame + var infiniteHeightFrame = oldFrame + infiniteHeightFrame.size.width -= 4.0 + infiniteHeightFrame.size.height = 10240 + newFrame = (content as NSString).boundingRect( + with: infiniteHeightFrame.size, options: [.usesLineFragmentOrigin], + attributes: [.font: contentTextField.font!] + ) + newFrame.size.width = max(newFrame.size.width, oldFrame.size.width) + newFrame.size.height += 4.0 + newFrame.origin = oldFrame.origin + newFrame.origin.y -= (newFrame.size.height - oldFrame.size.height) + contentTextField.frame = newFrame - var windowFrame = window?.frame ?? NSRect.zero - windowFrame.size.height += (newFrame.size.height - oldFrame.size.height) - window?.level = NSWindow.Level(Int(CGShieldingWindowLevel()) + 1) - window?.setFrame(windowFrame, display: true) - window?.center() - window?.makeKeyAndOrderFront(self) - NSApp.activate(ignoringOtherApps: true) - } + var windowFrame = window?.frame ?? NSRect.zero + windowFrame.size.height += (newFrame.size.height - oldFrame.size.height) + window?.level = NSWindow.Level(Int(CGShieldingWindowLevel()) + 1) + window?.setFrame(windowFrame, display: true) + window?.center() + window?.makeKeyAndOrderFront(self) + NSApp.activate(ignoringOtherApps: true) + } - @IBAction func confirmButtonAction(_: Any) { - delegate?.ctlNonModalAlertWindowDidConfirm(self) - window?.orderOut(self) - } + @IBAction func confirmButtonAction(_: Any) { + delegate?.ctlNonModalAlertWindowDidConfirm(self) + window?.orderOut(self) + } - @IBAction func cancelButtonAction(_ sender: Any) { - cancel(sender) - } + @IBAction func cancelButtonAction(_ sender: Any) { + cancel(sender) + } - func cancel(_: Any) { - delegate?.ctlNonModalAlertWindowDidCancel(self) - delegate = nil - window?.orderOut(self) - } + func cancel(_: Any) { + delegate?.ctlNonModalAlertWindowDidCancel(self) + delegate = nil + window?.orderOut(self) + } } diff --git a/Source/WindowControllers/ctlPrefWindow.swift b/Source/WindowControllers/ctlPrefWindow.swift index b0e68b29..81b87247 100644 --- a/Source/WindowControllers/ctlPrefWindow.swift +++ b/Source/WindowControllers/ctlPrefWindow.swift @@ -30,267 +30,267 @@ import Cocoa // Please note that the class should be exposed using the same class name // in Objective-C in order to let IMK to see the same class name as // the "InputMethodServerPreferencesWindowControllerClass" in Info.plist. -@objc(ctlPrefWindow) class ctlPrefWindow: NSWindowController { - @IBOutlet var fontSizePopUpButton: NSPopUpButton! - @IBOutlet var uiLanguageButton: NSPopUpButton! - @IBOutlet var basicKeyboardLayoutButton: NSPopUpButton! - @IBOutlet var selectionKeyComboBox: NSComboBox! - @IBOutlet var chkTrad2KangXi: NSButton! - @IBOutlet var chkTrad2JISShinjitai: NSButton! - @IBOutlet var lblCurrentlySpecifiedUserDataFolder: NSTextFieldCell! +class ctlPrefWindow: NSWindowController { + @IBOutlet var fontSizePopUpButton: NSPopUpButton! + @IBOutlet var uiLanguageButton: NSPopUpButton! + @IBOutlet var basicKeyboardLayoutButton: NSPopUpButton! + @IBOutlet var selectionKeyComboBox: NSComboBox! + @IBOutlet var chkTrad2KangXi: NSButton! + @IBOutlet var chkTrad2JISShinjitai: NSButton! + @IBOutlet var lblCurrentlySpecifiedUserDataFolder: NSTextFieldCell! - var currentLanguageSelectItem: NSMenuItem? + var currentLanguageSelectItem: NSMenuItem? - override func windowDidLoad() { - super.windowDidLoad() + override func windowDidLoad() { + super.windowDidLoad() - lblCurrentlySpecifiedUserDataFolder.placeholderString = mgrLangModel.dataFolderPath( - isDefaultFolder: true) + lblCurrentlySpecifiedUserDataFolder.placeholderString = mgrLangModel.dataFolderPath( + isDefaultFolder: true) - let languages = ["auto", "en", "zh-Hans", "zh-Hant", "ja"] - var autoMUISelectItem: NSMenuItem? - var chosenLanguageItem: NSMenuItem? - uiLanguageButton.menu?.removeAllItems() + let languages = ["auto", "en", "zh-Hans", "zh-Hant", "ja"] + var autoMUISelectItem: NSMenuItem? + var chosenLanguageItem: NSMenuItem? + uiLanguageButton.menu?.removeAllItems() - let appleLanguages = mgrPrefs.appleLanguages - for language in languages { - let menuItem = NSMenuItem() - menuItem.title = NSLocalizedString(language, comment: language) - menuItem.representedObject = language + let appleLanguages = mgrPrefs.appleLanguages + for language in languages { + let menuItem = NSMenuItem() + menuItem.title = NSLocalizedString(language, comment: language) + menuItem.representedObject = language - if language == "auto" { - autoMUISelectItem = menuItem - } + if language == "auto" { + autoMUISelectItem = menuItem + } - if !appleLanguages.isEmpty { - if appleLanguages[0] == language { - chosenLanguageItem = menuItem - } - } - uiLanguageButton.menu?.addItem(menuItem) - } + if !appleLanguages.isEmpty { + if appleLanguages[0] == language { + chosenLanguageItem = menuItem + } + } + uiLanguageButton.menu?.addItem(menuItem) + } - currentLanguageSelectItem = chosenLanguageItem ?? autoMUISelectItem - uiLanguageButton.select(currentLanguageSelectItem) + currentLanguageSelectItem = chosenLanguageItem ?? autoMUISelectItem + uiLanguageButton.select(currentLanguageSelectItem) - let list = TISCreateInputSourceList(nil, true).takeRetainedValue() as! [TISInputSource] - var usKeyboardLayoutItem: NSMenuItem? - var chosenBaseKeyboardLayoutItem: NSMenuItem? + let list = TISCreateInputSourceList(nil, true).takeRetainedValue() as! [TISInputSource] + var usKeyboardLayoutItem: NSMenuItem? + var chosenBaseKeyboardLayoutItem: NSMenuItem? - basicKeyboardLayoutButton.menu?.removeAllItems() + basicKeyboardLayoutButton.menu?.removeAllItems() - let itmAppleZhuyinBopomofo = NSMenuItem() - itmAppleZhuyinBopomofo.title = String( - format: NSLocalizedString("Apple Zhuyin Bopomofo (Dachen)", comment: "")) - itmAppleZhuyinBopomofo.representedObject = String( - "com.apple.keylayout.ZhuyinBopomofo") - basicKeyboardLayoutButton.menu?.addItem(itmAppleZhuyinBopomofo) + let itmAppleZhuyinBopomofo = NSMenuItem() + itmAppleZhuyinBopomofo.title = String( + format: NSLocalizedString("Apple Zhuyin Bopomofo (Dachen)", comment: "")) + itmAppleZhuyinBopomofo.representedObject = String( + "com.apple.keylayout.ZhuyinBopomofo") + basicKeyboardLayoutButton.menu?.addItem(itmAppleZhuyinBopomofo) - let itmAppleZhuyinEten = NSMenuItem() - itmAppleZhuyinEten.title = String( - format: NSLocalizedString("Apple Zhuyin Eten (Traditional)", comment: "")) - itmAppleZhuyinEten.representedObject = String("com.apple.keylayout.ZhuyinEten") - basicKeyboardLayoutButton.menu?.addItem(itmAppleZhuyinEten) + let itmAppleZhuyinEten = NSMenuItem() + itmAppleZhuyinEten.title = String( + format: NSLocalizedString("Apple Zhuyin Eten (Traditional)", comment: "")) + itmAppleZhuyinEten.representedObject = String("com.apple.keylayout.ZhuyinEten") + basicKeyboardLayoutButton.menu?.addItem(itmAppleZhuyinEten) - let basicKeyboardLayoutID = mgrPrefs.basicKeyboardLayout + let basicKeyboardLayoutID = mgrPrefs.basicKeyboardLayout - for source in list { - if let categoryPtr = TISGetInputSourceProperty(source, kTISPropertyInputSourceCategory) { - let category = Unmanaged.fromOpaque(categoryPtr).takeUnretainedValue() - if category != kTISCategoryKeyboardInputSource { - continue - } - } else { - continue - } + for source in list { + if let categoryPtr = TISGetInputSourceProperty(source, kTISPropertyInputSourceCategory) { + let category = Unmanaged.fromOpaque(categoryPtr).takeUnretainedValue() + if category != kTISCategoryKeyboardInputSource { + continue + } + } else { + continue + } - if let asciiCapablePtr = TISGetInputSourceProperty( - source, kTISPropertyInputSourceIsASCIICapable - ) { - let asciiCapable = Unmanaged.fromOpaque(asciiCapablePtr) - .takeUnretainedValue() - if asciiCapable != kCFBooleanTrue { - continue - } - } else { - continue - } + if let asciiCapablePtr = TISGetInputSourceProperty( + source, kTISPropertyInputSourceIsASCIICapable + ) { + let asciiCapable = Unmanaged.fromOpaque(asciiCapablePtr) + .takeUnretainedValue() + if asciiCapable != kCFBooleanTrue { + continue + } + } else { + continue + } - if let sourceTypePtr = TISGetInputSourceProperty(source, kTISPropertyInputSourceType) { - let sourceType = Unmanaged.fromOpaque(sourceTypePtr).takeUnretainedValue() - if sourceType != kTISTypeKeyboardLayout { - continue - } - } else { - continue - } + if let sourceTypePtr = TISGetInputSourceProperty(source, kTISPropertyInputSourceType) { + let sourceType = Unmanaged.fromOpaque(sourceTypePtr).takeUnretainedValue() + if sourceType != kTISTypeKeyboardLayout { + continue + } + } else { + continue + } - guard let sourceIDPtr = TISGetInputSourceProperty(source, kTISPropertyInputSourceID), - let localizedNamePtr = TISGetInputSourceProperty(source, kTISPropertyLocalizedName) - else { - continue - } + guard let sourceIDPtr = TISGetInputSourceProperty(source, kTISPropertyInputSourceID), + let localizedNamePtr = TISGetInputSourceProperty(source, kTISPropertyLocalizedName) + else { + continue + } - let sourceID = String(Unmanaged.fromOpaque(sourceIDPtr).takeUnretainedValue()) - let localizedName = String( - Unmanaged.fromOpaque(localizedNamePtr).takeUnretainedValue()) + let sourceID = String(Unmanaged.fromOpaque(sourceIDPtr).takeUnretainedValue()) + let localizedName = String( + Unmanaged.fromOpaque(localizedNamePtr).takeUnretainedValue()) - let menuItem = NSMenuItem() - menuItem.title = localizedName - menuItem.representedObject = sourceID + let menuItem = NSMenuItem() + menuItem.title = localizedName + menuItem.representedObject = sourceID - if sourceID == "com.apple.keylayout.US" { - usKeyboardLayoutItem = menuItem - } - if basicKeyboardLayoutID == sourceID { - chosenBaseKeyboardLayoutItem = menuItem - } - if IME.arrWhitelistedKeyLayoutsASCII.contains(sourceID) || sourceID.contains("vChewing") { - basicKeyboardLayoutButton.menu?.addItem(menuItem) - } - } + if sourceID == "com.apple.keylayout.US" { + usKeyboardLayoutItem = menuItem + } + if basicKeyboardLayoutID == sourceID { + chosenBaseKeyboardLayoutItem = menuItem + } + if IME.arrWhitelistedKeyLayoutsASCII.contains(sourceID) || sourceID.contains("vChewing") { + basicKeyboardLayoutButton.menu?.addItem(menuItem) + } + } - switch basicKeyboardLayoutID { - case "com.apple.keylayout.ZhuyinBopomofo": - chosenBaseKeyboardLayoutItem = itmAppleZhuyinBopomofo - case "com.apple.keylayout.ZhuyinEten": - chosenBaseKeyboardLayoutItem = itmAppleZhuyinEten - default: - break // nothing to do - } + switch basicKeyboardLayoutID { + case "com.apple.keylayout.ZhuyinBopomofo": + chosenBaseKeyboardLayoutItem = itmAppleZhuyinBopomofo + case "com.apple.keylayout.ZhuyinEten": + chosenBaseKeyboardLayoutItem = itmAppleZhuyinEten + default: + break // nothing to do + } - basicKeyboardLayoutButton.select(chosenBaseKeyboardLayoutItem ?? usKeyboardLayoutItem) + basicKeyboardLayoutButton.select(chosenBaseKeyboardLayoutItem ?? usKeyboardLayoutItem) - selectionKeyComboBox.usesDataSource = false - selectionKeyComboBox.removeAllItems() - selectionKeyComboBox.addItems(withObjectValues: mgrPrefs.suggestedCandidateKeys) + selectionKeyComboBox.usesDataSource = false + selectionKeyComboBox.removeAllItems() + selectionKeyComboBox.addItems(withObjectValues: mgrPrefs.suggestedCandidateKeys) - var candidateSelectionKeys = mgrPrefs.candidateKeys - if candidateSelectionKeys.isEmpty { - candidateSelectionKeys = mgrPrefs.defaultCandidateKeys - } + var candidateSelectionKeys = mgrPrefs.candidateKeys + if candidateSelectionKeys.isEmpty { + candidateSelectionKeys = mgrPrefs.defaultCandidateKeys + } - selectionKeyComboBox.stringValue = candidateSelectionKeys - } + selectionKeyComboBox.stringValue = candidateSelectionKeys + } - // 這裡有必要加上這段處理,用來確保藉由偏好設定介面動過的 CNS 開關能夠立刻生效。 - // 所有涉及到語言模型開關的內容均需要這樣處理。 - @IBAction func toggleCNSSupport(_: Any) { - mgrLangModel.setCNSEnabled(mgrPrefs.cns11643Enabled) - } + // 這裡有必要加上這段處理,用來確保藉由偏好設定介面動過的 CNS 開關能夠立刻生效。 + // 所有涉及到語言模型開關的內容均需要這樣處理。 + @IBAction func toggleCNSSupport(_: Any) { + mgrLangModel.setCNSEnabled(mgrPrefs.cns11643Enabled) + } - @IBAction func toggleSymbolInputEnabled(_: Any) { - mgrLangModel.setSymbolEnabled(mgrPrefs.symbolInputEnabled) - } + @IBAction func toggleSymbolInputEnabled(_: Any) { + mgrLangModel.setSymbolEnabled(mgrPrefs.symbolInputEnabled) + } - @IBAction func toggleTrad2KangXiAction(_: Any) { - if chkTrad2KangXi.state == .on, chkTrad2JISShinjitai.state == .on { - mgrPrefs.toggleShiftJISShinjitaiOutputEnabled() - } - } + @IBAction func toggleTrad2KangXiAction(_: Any) { + if chkTrad2KangXi.state == .on, chkTrad2JISShinjitai.state == .on { + mgrPrefs.toggleShiftJISShinjitaiOutputEnabled() + } + } - @IBAction func toggleTrad2JISShinjitaiAction(_: Any) { - if chkTrad2KangXi.state == .on, chkTrad2JISShinjitai.state == .on { - mgrPrefs.toggleChineseConversionEnabled() - } - } + @IBAction func toggleTrad2JISShinjitaiAction(_: Any) { + if chkTrad2KangXi.state == .on, chkTrad2JISShinjitai.state == .on { + mgrPrefs.toggleChineseConversionEnabled() + } + } - @IBAction func updateBasicKeyboardLayoutAction(_: Any) { - if let sourceID = basicKeyboardLayoutButton.selectedItem?.representedObject as? String { - mgrPrefs.basicKeyboardLayout = sourceID - } - } + @IBAction func updateBasicKeyboardLayoutAction(_: Any) { + if let sourceID = basicKeyboardLayoutButton.selectedItem?.representedObject as? String { + mgrPrefs.basicKeyboardLayout = sourceID + } + } - @IBAction func updateUiLanguageAction(_: Any) { - if let selectItem = uiLanguageButton.selectedItem { - if currentLanguageSelectItem == selectItem { - return - } - } - if let language = uiLanguageButton.selectedItem?.representedObject as? String { - if language != "auto" { - mgrPrefs.appleLanguages = [language] - } else { - UserDefaults.standard.removeObject(forKey: "AppleLanguages") - } + @IBAction func updateUiLanguageAction(_: Any) { + if let selectItem = uiLanguageButton.selectedItem { + if currentLanguageSelectItem == selectItem { + return + } + } + if let language = uiLanguageButton.selectedItem?.representedObject as? String { + if language != "auto" { + mgrPrefs.appleLanguages = [language] + } else { + UserDefaults.standard.removeObject(forKey: "AppleLanguages") + } - NSLog("vChewing App self-terminated due to UI language change.") - NSApplication.shared.terminate(nil) - } - } + NSLog("vChewing App self-terminated due to UI language change.") + NSApplication.shared.terminate(nil) + } + } - @IBAction func clickedWhetherIMEShouldNotFartToggleAction(_: Any) { - clsSFX.beep() - } + @IBAction func clickedWhetherIMEShouldNotFartToggleAction(_: Any) { + clsSFX.beep() + } - @IBAction func changeSelectionKeyAction(_ sender: Any) { - guard - let keys = (sender as AnyObject).stringValue?.trimmingCharacters( - in: .whitespacesAndNewlines - ) - .charDeDuplicate - else { - return - } - do { - try mgrPrefs.validate(candidateKeys: keys) - mgrPrefs.candidateKeys = keys - selectionKeyComboBox.stringValue = mgrPrefs.candidateKeys - } catch mgrPrefs.CandidateKeyError.empty { - selectionKeyComboBox.stringValue = mgrPrefs.candidateKeys - } catch { - if let window = window { - let alert = NSAlert(error: error) - alert.beginSheetModal(for: window) { _ in - self.selectionKeyComboBox.stringValue = mgrPrefs.candidateKeys - } - clsSFX.beep() - } - } - } + @IBAction func changeSelectionKeyAction(_ sender: Any) { + guard + let keys = (sender as AnyObject).stringValue?.trimmingCharacters( + in: .whitespacesAndNewlines + ) + .charDeDuplicate + else { + return + } + do { + try mgrPrefs.validate(candidateKeys: keys) + mgrPrefs.candidateKeys = keys + selectionKeyComboBox.stringValue = mgrPrefs.candidateKeys + } catch mgrPrefs.CandidateKeyError.empty { + selectionKeyComboBox.stringValue = mgrPrefs.candidateKeys + } catch { + if let window = window { + let alert = NSAlert(error: error) + alert.beginSheetModal(for: window) { _ in + self.selectionKeyComboBox.stringValue = mgrPrefs.candidateKeys + } + clsSFX.beep() + } + } + } - @IBAction func resetSpecifiedUserDataFolder(_: Any) { - mgrPrefs.resetSpecifiedUserDataFolder() - } + @IBAction func resetSpecifiedUserDataFolder(_: Any) { + mgrPrefs.resetSpecifiedUserDataFolder() + } - @IBAction func chooseUserDataFolderToSpecify(_: Any) { - IME.dlgOpenPath.title = NSLocalizedString( - "Choose your desired user data folder.", comment: "" - ) - IME.dlgOpenPath.showsResizeIndicator = true - IME.dlgOpenPath.showsHiddenFiles = true - IME.dlgOpenPath.canChooseFiles = false - IME.dlgOpenPath.canChooseDirectories = true + @IBAction func chooseUserDataFolderToSpecify(_: Any) { + IME.dlgOpenPath.title = NSLocalizedString( + "Choose your desired user data folder.", comment: "" + ) + IME.dlgOpenPath.showsResizeIndicator = true + IME.dlgOpenPath.showsHiddenFiles = true + IME.dlgOpenPath.canChooseFiles = false + IME.dlgOpenPath.canChooseDirectories = true - let bolPreviousFolderValidity = mgrLangModel.checkIfSpecifiedUserDataFolderValid( - NSString(string: mgrPrefs.userDataFolderSpecified).expandingTildeInPath) + let bolPreviousFolderValidity = mgrLangModel.checkIfSpecifiedUserDataFolderValid( + NSString(string: mgrPrefs.userDataFolderSpecified).expandingTildeInPath) - if window != nil { - IME.dlgOpenPath.beginSheetModal(for: window!) { result in - if result == NSApplication.ModalResponse.OK { - if IME.dlgOpenPath.url != nil { - // CommonDialog 讀入的路徑沒有結尾斜槓,這會導致檔案目錄合規性判定失準。 - // 所以要手動補回來。 - var newPath = IME.dlgOpenPath.url!.path - newPath.ensureTrailingSlash() - if mgrLangModel.checkIfSpecifiedUserDataFolderValid(newPath) { - mgrPrefs.userDataFolderSpecified = newPath - IME.initLangModels(userOnly: true) - } else { - clsSFX.beep() - if !bolPreviousFolderValidity { - mgrPrefs.resetSpecifiedUserDataFolder() - } - return - } - } - } else { - if !bolPreviousFolderValidity { - mgrPrefs.resetSpecifiedUserDataFolder() - } - return - } - } - } // End If self.window != nil - } // End IBAction + if window != nil { + IME.dlgOpenPath.beginSheetModal(for: window!) { result in + if result == NSApplication.ModalResponse.OK { + if IME.dlgOpenPath.url != nil { + // CommonDialog 讀入的路徑沒有結尾斜槓,這會導致檔案目錄合規性判定失準。 + // 所以要手動補回來。 + var newPath = IME.dlgOpenPath.url!.path + newPath.ensureTrailingSlash() + if mgrLangModel.checkIfSpecifiedUserDataFolderValid(newPath) { + mgrPrefs.userDataFolderSpecified = newPath + IME.initLangModels(userOnly: true) + } else { + clsSFX.beep() + if !bolPreviousFolderValidity { + mgrPrefs.resetSpecifiedUserDataFolder() + } + return + } + } + } else { + if !bolPreviousFolderValidity { + mgrPrefs.resetSpecifiedUserDataFolder() + } + return + } + } + } // End If self.window != nil + } // End IBAction } diff --git a/UserPhraseEditor/AppDelegate.swift b/UserPhraseEditor/AppDelegate.swift index 5662a1e9..d3b76326 100644 --- a/UserPhraseEditor/AppDelegate.swift +++ b/UserPhraseEditor/AppDelegate.swift @@ -26,33 +26,33 @@ import Cocoa @NSApplicationMain class AppDelegate: NSObject, NSApplicationDelegate { - private var ctlAboutWindowInstance: ctlAboutWindow? // New About Window + private var ctlAboutWindowInstance: ctlAboutWindow? // New About Window - func applicationDidFinishLaunching(_: Notification) { - // Insert code here to initialize your application - } + func applicationDidFinishLaunching(_: Notification) { + // Insert code here to initialize your application + } - func applicationWillTerminate(_: Notification) { - // Insert code here to tear down your application - } + func applicationWillTerminate(_: Notification) { + // Insert code here to tear down your application + } - func applicationShouldTerminate(_: NSApplication) -> NSApplication.TerminateReply { - .terminateNow - } + func applicationShouldTerminate(_: NSApplication) -> NSApplication.TerminateReply { + .terminateNow + } - // New About Window - @objc func showAbout() { - if ctlAboutWindowInstance == nil { - ctlAboutWindowInstance = ctlAboutWindow.init(windowNibName: "frmAboutWindow") - } - ctlAboutWindowInstance?.window?.center() - ctlAboutWindowInstance?.window?.orderFrontRegardless() // 逼著關於視窗往最前方顯示 - ctlAboutWindowInstance?.window?.level = .statusBar - } + // New About Window + @objc func showAbout() { + if ctlAboutWindowInstance == nil { + ctlAboutWindowInstance = ctlAboutWindow.init(windowNibName: "frmAboutWindow") + } + ctlAboutWindowInstance?.window?.center() + ctlAboutWindowInstance?.window?.orderFrontRegardless() // 逼著關於視窗往最前方顯示 + ctlAboutWindowInstance?.window?.level = .statusBar + } - // Call the New About Window - @IBAction func about(_: Any) { - (NSApp.delegate as? AppDelegate)?.showAbout() - NSApplication.shared.activate(ignoringOtherApps: true) - } + // Call the New About Window + @IBAction func about(_: Any) { + (NSApp.delegate as? AppDelegate)?.showAbout() + NSApplication.shared.activate(ignoringOtherApps: true) + } } diff --git a/UserPhraseEditor/Content.swift b/UserPhraseEditor/Content.swift index 2988eebb..0b7ab535 100644 --- a/UserPhraseEditor/Content.swift +++ b/UserPhraseEditor/Content.swift @@ -26,19 +26,19 @@ import Cocoa import Foundation class Content: NSObject { - @objc dynamic var contentString = "" + @objc dynamic var contentString = "" - public init(contentString: String) { - self.contentString = contentString - } + public init(contentString: String) { + self.contentString = contentString + } } extension Content { - func read(from data: Data) { - contentString = String(bytes: data, encoding: .utf8)! - } + func read(from data: Data) { + contentString = String(bytes: data, encoding: .utf8)! + } - func data() -> Data? { - contentString.data(using: .utf8) - } + func data() -> Data? { + contentString.data(using: .utf8) + } } diff --git a/UserPhraseEditor/Document.swift b/UserPhraseEditor/Document.swift index ce81cb88..e7c025c2 100644 --- a/UserPhraseEditor/Document.swift +++ b/UserPhraseEditor/Document.swift @@ -25,122 +25,122 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import Cocoa class Document: NSDocument { - @objc var content = Content(contentString: "") - var contentViewController: ViewController! + @objc var content = Content(contentString: "") + var contentViewController: ViewController! - override init() { - super.init() - // Add your subclass-specific initialization here. - } + override init() { + super.init() + // Add your subclass-specific initialization here. + } - // MARK: - Enablers + // MARK: - Enablers - // This enables auto save. - override class var autosavesInPlace: Bool { - true - } + // This enables auto save. + override class var autosavesInPlace: Bool { + true + } - // This enables asynchronous-writing. - override func canAsynchronouslyWrite( - to _: URL, ofType _: String, for _: NSDocument.SaveOperationType - ) -> Bool { - true - } + // This enables asynchronous-writing. + override func canAsynchronouslyWrite( + to _: URL, ofType _: String, for _: NSDocument.SaveOperationType + ) -> Bool { + true + } - // This enables asynchronous reading. - override class func canConcurrentlyReadDocuments(ofType: String) -> Bool { - ofType == "public.plain-text" - } + // This enables asynchronous reading. + override class func canConcurrentlyReadDocuments(ofType: String) -> Bool { + ofType == "public.plain-text" + } - // MARK: - User Interface + // MARK: - User Interface - /// - Tag: makeWindowControllersExample - override func makeWindowControllers() { - // Returns the storyboard that contains your document window. - let storyboard = NSStoryboard(name: NSStoryboard.Name("Main"), bundle: nil) - if let windowController = - storyboard.instantiateController( - withIdentifier: NSStoryboard.SceneIdentifier("Document Window Controller")) - as? NSWindowController - { - addWindowController(windowController) + /// - Tag: makeWindowControllersExample + override func makeWindowControllers() { + // Returns the storyboard that contains your document window. + let storyboard = NSStoryboard(name: NSStoryboard.Name("Main"), bundle: nil) + if let windowController = + storyboard.instantiateController( + withIdentifier: NSStoryboard.SceneIdentifier("Document Window Controller")) + as? NSWindowController + { + addWindowController(windowController) - // Set the view controller's represented object as your document. - if let contentVC = windowController.contentViewController as? ViewController { - contentVC.representedObject = content - contentViewController = contentVC - } - } - } + // Set the view controller's represented object as your document. + if let contentVC = windowController.contentViewController as? ViewController { + contentVC.representedObject = content + contentViewController = contentVC + } + } + } - // MARK: - Reading and Writing + // MARK: - Reading and Writing - /// - Tag: readExample - override func read(from data: Data, ofType _: String) throws { - var strToDealWith = String(decoding: data, as: UTF8.self) - strToDealWith.formatConsolidate(cnvHYPYtoBPMF: false) - let processedIncomingData = Data(strToDealWith.utf8) - content.read(from: processedIncomingData) - } + /// - Tag: readExample + override func read(from data: Data, ofType _: String) throws { + var strToDealWith = String(decoding: data, as: UTF8.self) + strToDealWith.formatConsolidate(cnvHYPYtoBPMF: false) + let processedIncomingData = Data(strToDealWith.utf8) + content.read(from: processedIncomingData) + } - /// - Tag: writeExample - override func data(ofType _: String) throws -> Data { - var strToDealWith = content.contentString - strToDealWith.formatConsolidate(cnvHYPYtoBPMF: true) - let outputData = Data(strToDealWith.utf8) - return outputData - } + /// - Tag: writeExample + override func data(ofType _: String) throws -> Data { + var strToDealWith = content.contentString + strToDealWith.formatConsolidate(cnvHYPYtoBPMF: true) + let outputData = Data(strToDealWith.utf8) + return outputData + } - // MARK: - Printing + // MARK: - Printing - func thePrintInfo() -> NSPrintInfo { - let thePrintInfo = NSPrintInfo() - thePrintInfo.horizontalPagination = .fit - thePrintInfo.isHorizontallyCentered = false - thePrintInfo.isVerticallyCentered = false + func thePrintInfo() -> NSPrintInfo { + let thePrintInfo = NSPrintInfo() + thePrintInfo.horizontalPagination = .fit + thePrintInfo.isHorizontallyCentered = false + thePrintInfo.isVerticallyCentered = false - // One inch margin all the way around. - thePrintInfo.leftMargin = 72.0 - thePrintInfo.rightMargin = 72.0 - thePrintInfo.topMargin = 72.0 - thePrintInfo.bottomMargin = 72.0 + // One inch margin all the way around. + thePrintInfo.leftMargin = 72.0 + thePrintInfo.rightMargin = 72.0 + thePrintInfo.topMargin = 72.0 + thePrintInfo.bottomMargin = 72.0 - printInfo.dictionary().setObject( - NSNumber(value: true), - forKey: NSPrintInfo.AttributeKey.headerAndFooter as NSCopying - ) + printInfo.dictionary().setObject( + NSNumber(value: true), + forKey: NSPrintInfo.AttributeKey.headerAndFooter as NSCopying + ) - return thePrintInfo - } + return thePrintInfo + } - @objc - func printOperationDidRun( - _: NSPrintOperation, success _: Bool, contextInfo _: UnsafeMutableRawPointer? - ) { - // Printing finished... - } + @objc + func printOperationDidRun( + _: NSPrintOperation, success _: Bool, contextInfo _: UnsafeMutableRawPointer? + ) { + // Printing finished... + } - @IBAction override func printDocument(_: Any?) { - // Print the NSTextView. + @IBAction override func printDocument(_: Any?) { + // Print the NSTextView. - // Create a copy to manipulate for printing. - let pageSize = NSSize( - width: printInfo.paperSize.width, height: printInfo.paperSize.height - ) - let textView = NSTextView( - frame: NSRect(x: 0.0, y: 0.0, width: pageSize.width, height: pageSize.height)) + // Create a copy to manipulate for printing. + let pageSize = NSSize( + width: printInfo.paperSize.width, height: printInfo.paperSize.height + ) + let textView = NSTextView( + frame: NSRect(x: 0.0, y: 0.0, width: pageSize.width, height: pageSize.height)) - // Make sure we print on a white background. - textView.appearance = NSAppearance(named: .aqua) + // Make sure we print on a white background. + textView.appearance = NSAppearance(named: .aqua) - // Copy the attributed string. - textView.textStorage?.append(NSAttributedString(string: content.contentString)) + // Copy the attributed string. + textView.textStorage?.append(NSAttributedString(string: content.contentString)) - let printOperation = NSPrintOperation(view: textView) - printOperation.runModal( - for: windowControllers[0].window!, - delegate: self, - didRun: #selector(printOperationDidRun(_:success:contextInfo:)), contextInfo: nil - ) - } + let printOperation = NSPrintOperation(view: textView) + printOperation.runModal( + for: windowControllers[0].window!, + delegate: self, + didRun: #selector(printOperationDidRun(_:success:contextInfo:)), contextInfo: nil + ) + } } diff --git a/UserPhraseEditor/StringExtension.swift b/UserPhraseEditor/StringExtension.swift index ca81a44b..e05e1661 100644 --- a/UserPhraseEditor/StringExtension.swift +++ b/UserPhraseEditor/StringExtension.swift @@ -25,503 +25,503 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import Foundation extension String { - mutating func regReplace(pattern: String, replaceWith: String = "") { - // Ref: https://stackoverflow.com/a/40993403/4162914 && https://stackoverflow.com/a/71291137/4162914 - do { - let regex = try NSRegularExpression( - pattern: pattern, options: [.caseInsensitive, .anchorsMatchLines] - ) - let range = NSRange(startIndex..., in: self) - self = regex.stringByReplacingMatches( - in: self, options: [], range: range, withTemplate: replaceWith - ) - } catch { return } - } + mutating func regReplace(pattern: String, replaceWith: String = "") { + // Ref: https://stackoverflow.com/a/40993403/4162914 && https://stackoverflow.com/a/71291137/4162914 + do { + let regex = try NSRegularExpression( + pattern: pattern, options: [.caseInsensitive, .anchorsMatchLines] + ) + let range = NSRange(startIndex..., in: self) + self = regex.stringByReplacingMatches( + in: self, options: [], range: range, withTemplate: replaceWith + ) + } catch { return } + } - mutating func selfReplace(_ strOf: String, _ strWith: String = "") { - self = replacingOccurrences(of: strOf, with: strWith) - } + mutating func selfReplace(_ strOf: String, _ strWith: String = "") { + self = replacingOccurrences(of: strOf, with: strWith) + } - mutating func formatConsolidate(cnvHYPYtoBPMF: Bool) { - // Step 1: Consolidating formats per line. - var strProcessed = self - // 預處理格式 - strProcessed = strProcessed.replacingOccurrences(of: " #MACOS", with: "") // 去掉 macOS 標記 - // CJKWhiteSpace (\x{3000}) to ASCII Space - // NonBreakWhiteSpace (\x{A0}) to ASCII Space - // Tab to ASCII Space - // 統整連續空格為一個 ASCII 空格 - strProcessed.regReplace(pattern: #"( +| +| +|\t+)+"#, replaceWith: " ") - strProcessed.regReplace(pattern: #"(^ | $)"#, replaceWith: "") // 去除行尾行首空格 - strProcessed.regReplace(pattern: #"(\f+|\r+|\n+)+"#, replaceWith: "\n") // CR & FF to LF, 且去除重複行 - if strProcessed.prefix(1) == " " { // 去除檔案開頭空格 - strProcessed.removeFirst() - } - if strProcessed.suffix(1) == " " { // 去除檔案結尾空格 - strProcessed.removeLast() - } - var arrData = [""] - if cnvHYPYtoBPMF { - // Step 0: Convert HanyuPinyin to Bopomofo. - arrData = strProcessed.components(separatedBy: "\n") - strProcessed = "" // Reset its value - for lineData in arrData { - var varLineData = lineData - // 漢語拼音轉注音,得先從最長的可能的拼音組合開始轉起, - // 這樣等轉換到更短的可能的漢語拼音組合時就不會出錯。 - // 依此類推,聲調放在最後來轉換。 - varLineData.selfReplace("chuang", "ㄔㄨㄤ") - varLineData.selfReplace("shuang", "ㄕㄨㄤ") - varLineData.selfReplace("zhuang", "ㄓㄨㄤ") - varLineData.selfReplace("chang", "ㄔㄤ") - varLineData.selfReplace("cheng", "ㄔㄥ") - varLineData.selfReplace("chong", "ㄔㄨㄥ") - varLineData.selfReplace("chuai", "ㄔㄨㄞ") - varLineData.selfReplace("chuan", "ㄔㄨㄢ") - varLineData.selfReplace("guang", "ㄍㄨㄤ") - varLineData.selfReplace("huang", "ㄏㄨㄤ") - varLineData.selfReplace("jiang", "ㄐㄧㄤ") - varLineData.selfReplace("jiong", "ㄐㄩㄥ") - varLineData.selfReplace("kuang", "ㄎㄨㄤ") - varLineData.selfReplace("liang", "ㄌㄧㄤ") - varLineData.selfReplace("niang", "ㄋㄧㄤ") - varLineData.selfReplace("qiang", "ㄑㄧㄤ") - varLineData.selfReplace("qiong", "ㄑㄩㄥ") - varLineData.selfReplace("shang", "ㄕㄤ") - varLineData.selfReplace("sheng", "ㄕㄥ") - varLineData.selfReplace("shuai", "ㄕㄨㄞ") - varLineData.selfReplace("shuan", "ㄕㄨㄢ") - varLineData.selfReplace("xiang", "ㄒㄧㄤ") - varLineData.selfReplace("xiong", "ㄒㄩㄥ") - varLineData.selfReplace("zhang", "ㄓㄤ") - varLineData.selfReplace("zheng", "ㄓㄥ") - varLineData.selfReplace("zhong", "ㄓㄨㄥ") - varLineData.selfReplace("zhuai", "ㄓㄨㄞ") - varLineData.selfReplace("zhuan", "ㄓㄨㄢ") - varLineData.selfReplace("bang", "ㄅㄤ") - varLineData.selfReplace("beng", "ㄅㄥ") - varLineData.selfReplace("bian", "ㄅㄧㄢ") - varLineData.selfReplace("biao", "ㄅㄧㄠ") - varLineData.selfReplace("bing", "ㄅㄧㄥ") - varLineData.selfReplace("cang", "ㄘㄤ") - varLineData.selfReplace("ceng", "ㄘㄥ") - varLineData.selfReplace("chai", "ㄔㄞ") - varLineData.selfReplace("chan", "ㄔㄢ") - varLineData.selfReplace("chao", "ㄔㄠ") - varLineData.selfReplace("chen", "ㄔㄣ") - varLineData.selfReplace("chou", "ㄔㄡ") - varLineData.selfReplace("chua", "ㄔㄨㄚ") - varLineData.selfReplace("chui", "ㄔㄨㄟ") - varLineData.selfReplace("chun", "ㄔㄨㄣ") - varLineData.selfReplace("chuo", "ㄔㄨㄛ") - varLineData.selfReplace("cong", "ㄘㄨㄥ") - varLineData.selfReplace("cuan", "ㄘㄨㄢ") - varLineData.selfReplace("dang", "ㄉㄤ") - varLineData.selfReplace("deng", "ㄉㄥ") - varLineData.selfReplace("dian", "ㄉㄧㄢ") - varLineData.selfReplace("diao", "ㄉㄧㄠ") - varLineData.selfReplace("ding", "ㄉㄧㄥ") - varLineData.selfReplace("dong", "ㄉㄨㄥ") - varLineData.selfReplace("duan", "ㄉㄨㄢ") - varLineData.selfReplace("fang", "ㄈㄤ") - varLineData.selfReplace("feng", "ㄈㄥ") - varLineData.selfReplace("fiao", "ㄈㄧㄠ") - varLineData.selfReplace("fong", "ㄈㄨㄥ") - varLineData.selfReplace("gang", "ㄍㄤ") - varLineData.selfReplace("geng", "ㄍㄥ") - varLineData.selfReplace("giao", "ㄍㄧㄠ") - varLineData.selfReplace("gong", "ㄍㄨㄥ") - varLineData.selfReplace("guai", "ㄍㄨㄞ") - varLineData.selfReplace("guan", "ㄍㄨㄢ") - varLineData.selfReplace("hang", "ㄏㄤ") - varLineData.selfReplace("heng", "ㄏㄥ") - varLineData.selfReplace("hong", "ㄏㄨㄥ") - varLineData.selfReplace("huai", "ㄏㄨㄞ") - varLineData.selfReplace("huan", "ㄏㄨㄢ") - varLineData.selfReplace("jian", "ㄐㄧㄢ") - varLineData.selfReplace("jiao", "ㄐㄧㄠ") - varLineData.selfReplace("jing", "ㄐㄧㄥ") - varLineData.selfReplace("juan", "ㄐㄩㄢ") - varLineData.selfReplace("kang", "ㄎㄤ") - varLineData.selfReplace("keng", "ㄎㄥ") - varLineData.selfReplace("kong", "ㄎㄨㄥ") - varLineData.selfReplace("kuai", "ㄎㄨㄞ") - varLineData.selfReplace("kuan", "ㄎㄨㄢ") - varLineData.selfReplace("lang", "ㄌㄤ") - varLineData.selfReplace("leng", "ㄌㄥ") - varLineData.selfReplace("lian", "ㄌㄧㄢ") - varLineData.selfReplace("liao", "ㄌㄧㄠ") - varLineData.selfReplace("ling", "ㄌㄧㄥ") - varLineData.selfReplace("long", "ㄌㄨㄥ") - varLineData.selfReplace("luan", "ㄌㄨㄢ") - varLineData.selfReplace("lvan", "ㄌㄩㄢ") - varLineData.selfReplace("mang", "ㄇㄤ") - varLineData.selfReplace("meng", "ㄇㄥ") - varLineData.selfReplace("mian", "ㄇㄧㄢ") - varLineData.selfReplace("miao", "ㄇㄧㄠ") - varLineData.selfReplace("ming", "ㄇㄧㄥ") - varLineData.selfReplace("nang", "ㄋㄤ") - varLineData.selfReplace("neng", "ㄋㄥ") - varLineData.selfReplace("nian", "ㄋㄧㄢ") - varLineData.selfReplace("niao", "ㄋㄧㄠ") - varLineData.selfReplace("ning", "ㄋㄧㄥ") - varLineData.selfReplace("nong", "ㄋㄨㄥ") - varLineData.selfReplace("nuan", "ㄋㄨㄢ") - varLineData.selfReplace("pang", "ㄆㄤ") - varLineData.selfReplace("peng", "ㄆㄥ") - varLineData.selfReplace("pian", "ㄆㄧㄢ") - varLineData.selfReplace("piao", "ㄆㄧㄠ") - varLineData.selfReplace("ping", "ㄆㄧㄥ") - varLineData.selfReplace("qian", "ㄑㄧㄢ") - varLineData.selfReplace("qiao", "ㄑㄧㄠ") - varLineData.selfReplace("qing", "ㄑㄧㄥ") - varLineData.selfReplace("quan", "ㄑㄩㄢ") - varLineData.selfReplace("rang", "ㄖㄤ") - varLineData.selfReplace("reng", "ㄖㄥ") - varLineData.selfReplace("rong", "ㄖㄨㄥ") - varLineData.selfReplace("ruan", "ㄖㄨㄢ") - varLineData.selfReplace("sang", "ㄙㄤ") - varLineData.selfReplace("seng", "ㄙㄥ") - varLineData.selfReplace("shai", "ㄕㄞ") - varLineData.selfReplace("shan", "ㄕㄢ") - varLineData.selfReplace("shao", "ㄕㄠ") - varLineData.selfReplace("shei", "ㄕㄟ") - varLineData.selfReplace("shen", "ㄕㄣ") - varLineData.selfReplace("shou", "ㄕㄡ") - varLineData.selfReplace("shua", "ㄕㄨㄚ") - varLineData.selfReplace("shui", "ㄕㄨㄟ") - varLineData.selfReplace("shun", "ㄕㄨㄣ") - varLineData.selfReplace("shuo", "ㄕㄨㄛ") - varLineData.selfReplace("song", "ㄙㄨㄥ") - varLineData.selfReplace("suan", "ㄙㄨㄢ") - varLineData.selfReplace("tang", "ㄊㄤ") - varLineData.selfReplace("teng", "ㄊㄥ") - varLineData.selfReplace("tian", "ㄊㄧㄢ") - varLineData.selfReplace("tiao", "ㄊㄧㄠ") - varLineData.selfReplace("ting", "ㄊㄧㄥ") - varLineData.selfReplace("tong", "ㄊㄨㄥ") - varLineData.selfReplace("tuan", "ㄊㄨㄢ") - varLineData.selfReplace("wang", "ㄨㄤ") - varLineData.selfReplace("weng", "ㄨㄥ") - varLineData.selfReplace("xian", "ㄒㄧㄢ") - varLineData.selfReplace("xiao", "ㄒㄧㄠ") - varLineData.selfReplace("xing", "ㄒㄧㄥ") - varLineData.selfReplace("xuan", "ㄒㄩㄢ") - varLineData.selfReplace("yang", "ㄧㄤ") - varLineData.selfReplace("ying", "ㄧㄥ") - varLineData.selfReplace("yong", "ㄩㄥ") - varLineData.selfReplace("yuan", "ㄩㄢ") - varLineData.selfReplace("zang", "ㄗㄤ") - varLineData.selfReplace("zeng", "ㄗㄥ") - varLineData.selfReplace("zhai", "ㄓㄞ") - varLineData.selfReplace("zhan", "ㄓㄢ") - varLineData.selfReplace("zhao", "ㄓㄠ") - varLineData.selfReplace("zhei", "ㄓㄟ") - varLineData.selfReplace("zhen", "ㄓㄣ") - varLineData.selfReplace("zhou", "ㄓㄡ") - varLineData.selfReplace("zhua", "ㄓㄨㄚ") - varLineData.selfReplace("zhui", "ㄓㄨㄟ") - varLineData.selfReplace("zhun", "ㄓㄨㄣ") - varLineData.selfReplace("zhuo", "ㄓㄨㄛ") - varLineData.selfReplace("zong", "ㄗㄨㄥ") - varLineData.selfReplace("zuan", "ㄗㄨㄢ") - varLineData.selfReplace("jun", "ㄐㄩㄣ") - varLineData.selfReplace("ang", "ㄤ") - varLineData.selfReplace("bai", "ㄅㄞ") - varLineData.selfReplace("ban", "ㄅㄢ") - varLineData.selfReplace("bao", "ㄅㄠ") - varLineData.selfReplace("bei", "ㄅㄟ") - varLineData.selfReplace("ben", "ㄅㄣ") - varLineData.selfReplace("bie", "ㄅㄧㄝ") - varLineData.selfReplace("bin", "ㄅㄧㄣ") - varLineData.selfReplace("cai", "ㄘㄞ") - varLineData.selfReplace("can", "ㄘㄢ") - varLineData.selfReplace("cao", "ㄘㄠ") - varLineData.selfReplace("cei", "ㄘㄟ") - varLineData.selfReplace("cen", "ㄘㄣ") - varLineData.selfReplace("cha", "ㄔㄚ") - varLineData.selfReplace("che", "ㄔㄜ") - varLineData.selfReplace("chi", "ㄔ") - varLineData.selfReplace("chu", "ㄔㄨ") - varLineData.selfReplace("cou", "ㄘㄡ") - varLineData.selfReplace("cui", "ㄘㄨㄟ") - varLineData.selfReplace("cun", "ㄘㄨㄣ") - varLineData.selfReplace("cuo", "ㄘㄨㄛ") - varLineData.selfReplace("dai", "ㄉㄞ") - varLineData.selfReplace("dan", "ㄉㄢ") - varLineData.selfReplace("dao", "ㄉㄠ") - varLineData.selfReplace("dei", "ㄉㄟ") - varLineData.selfReplace("den", "ㄉㄣ") - varLineData.selfReplace("dia", "ㄉㄧㄚ") - varLineData.selfReplace("die", "ㄉㄧㄝ") - varLineData.selfReplace("diu", "ㄉㄧㄡ") - varLineData.selfReplace("dou", "ㄉㄡ") - varLineData.selfReplace("dui", "ㄉㄨㄟ") - varLineData.selfReplace("dun", "ㄉㄨㄣ") - varLineData.selfReplace("duo", "ㄉㄨㄛ") - varLineData.selfReplace("eng", "ㄥ") - varLineData.selfReplace("fan", "ㄈㄢ") - varLineData.selfReplace("fei", "ㄈㄟ") - varLineData.selfReplace("fen", "ㄈㄣ") - varLineData.selfReplace("fou", "ㄈㄡ") - varLineData.selfReplace("gai", "ㄍㄞ") - varLineData.selfReplace("gan", "ㄍㄢ") - varLineData.selfReplace("gao", "ㄍㄠ") - varLineData.selfReplace("gei", "ㄍㄟ") - varLineData.selfReplace("gin", "ㄍㄧㄣ") - varLineData.selfReplace("gen", "ㄍㄣ") - varLineData.selfReplace("gou", "ㄍㄡ") - varLineData.selfReplace("gua", "ㄍㄨㄚ") - varLineData.selfReplace("gue", "ㄍㄨㄜ") - varLineData.selfReplace("gui", "ㄍㄨㄟ") - varLineData.selfReplace("gun", "ㄍㄨㄣ") - varLineData.selfReplace("guo", "ㄍㄨㄛ") - varLineData.selfReplace("hai", "ㄏㄞ") - varLineData.selfReplace("han", "ㄏㄢ") - varLineData.selfReplace("hao", "ㄏㄠ") - varLineData.selfReplace("hei", "ㄏㄟ") - varLineData.selfReplace("hen", "ㄏㄣ") - varLineData.selfReplace("hou", "ㄏㄡ") - varLineData.selfReplace("hua", "ㄏㄨㄚ") - varLineData.selfReplace("hui", "ㄏㄨㄟ") - varLineData.selfReplace("hun", "ㄏㄨㄣ") - varLineData.selfReplace("huo", "ㄏㄨㄛ") - varLineData.selfReplace("jia", "ㄐㄧㄚ") - varLineData.selfReplace("jie", "ㄐㄧㄝ") - varLineData.selfReplace("jin", "ㄐㄧㄣ") - varLineData.selfReplace("jiu", "ㄐㄧㄡ") - varLineData.selfReplace("jue", "ㄐㄩㄝ") - varLineData.selfReplace("kai", "ㄎㄞ") - varLineData.selfReplace("kan", "ㄎㄢ") - varLineData.selfReplace("kao", "ㄎㄠ") - varLineData.selfReplace("ken", "ㄎㄣ") - varLineData.selfReplace("kiu", "ㄎㄧㄡ") - varLineData.selfReplace("kou", "ㄎㄡ") - varLineData.selfReplace("kua", "ㄎㄨㄚ") - varLineData.selfReplace("kui", "ㄎㄨㄟ") - varLineData.selfReplace("kun", "ㄎㄨㄣ") - varLineData.selfReplace("kuo", "ㄎㄨㄛ") - varLineData.selfReplace("lai", "ㄌㄞ") - varLineData.selfReplace("lan", "ㄌㄢ") - varLineData.selfReplace("lao", "ㄌㄠ") - varLineData.selfReplace("lei", "ㄌㄟ") - varLineData.selfReplace("lia", "ㄌㄧㄚ") - varLineData.selfReplace("lie", "ㄌㄧㄝ") - varLineData.selfReplace("lin", "ㄌㄧㄣ") - varLineData.selfReplace("liu", "ㄌㄧㄡ") - varLineData.selfReplace("lou", "ㄌㄡ") - varLineData.selfReplace("lun", "ㄌㄨㄣ") - varLineData.selfReplace("luo", "ㄌㄨㄛ") - varLineData.selfReplace("lve", "ㄌㄩㄝ") - varLineData.selfReplace("mai", "ㄇㄞ") - varLineData.selfReplace("man", "ㄇㄢ") - varLineData.selfReplace("mao", "ㄇㄠ") - varLineData.selfReplace("mei", "ㄇㄟ") - varLineData.selfReplace("men", "ㄇㄣ") - varLineData.selfReplace("mie", "ㄇㄧㄝ") - varLineData.selfReplace("min", "ㄇㄧㄣ") - varLineData.selfReplace("miu", "ㄇㄧㄡ") - varLineData.selfReplace("mou", "ㄇㄡ") - varLineData.selfReplace("nai", "ㄋㄞ") - varLineData.selfReplace("nan", "ㄋㄢ") - varLineData.selfReplace("nao", "ㄋㄠ") - varLineData.selfReplace("nei", "ㄋㄟ") - varLineData.selfReplace("nen", "ㄋㄣ") - varLineData.selfReplace("nie", "ㄋㄧㄝ") - varLineData.selfReplace("nin", "ㄋㄧㄣ") - varLineData.selfReplace("niu", "ㄋㄧㄡ") - varLineData.selfReplace("nou", "ㄋㄡ") - varLineData.selfReplace("nui", "ㄋㄨㄟ") - varLineData.selfReplace("nun", "ㄋㄨㄣ") - varLineData.selfReplace("nuo", "ㄋㄨㄛ") - varLineData.selfReplace("nve", "ㄋㄩㄝ") - varLineData.selfReplace("pai", "ㄆㄞ") - varLineData.selfReplace("pan", "ㄆㄢ") - varLineData.selfReplace("pao", "ㄆㄠ") - varLineData.selfReplace("pei", "ㄆㄟ") - varLineData.selfReplace("pen", "ㄆㄣ") - varLineData.selfReplace("pia", "ㄆㄧㄚ") - varLineData.selfReplace("pie", "ㄆㄧㄝ") - varLineData.selfReplace("pin", "ㄆㄧㄣ") - varLineData.selfReplace("pou", "ㄆㄡ") - varLineData.selfReplace("qia", "ㄑㄧㄚ") - varLineData.selfReplace("qie", "ㄑㄧㄝ") - varLineData.selfReplace("qin", "ㄑㄧㄣ") - varLineData.selfReplace("qiu", "ㄑㄧㄡ") - varLineData.selfReplace("que", "ㄑㄩㄝ") - varLineData.selfReplace("qun", "ㄑㄩㄣ") - varLineData.selfReplace("ran", "ㄖㄢ") - varLineData.selfReplace("rao", "ㄖㄠ") - varLineData.selfReplace("ren", "ㄖㄣ") - varLineData.selfReplace("rou", "ㄖㄡ") - varLineData.selfReplace("rui", "ㄖㄨㄟ") - varLineData.selfReplace("run", "ㄖㄨㄣ") - varLineData.selfReplace("ruo", "ㄖㄨㄛ") - varLineData.selfReplace("sai", "ㄙㄞ") - varLineData.selfReplace("san", "ㄙㄢ") - varLineData.selfReplace("sao", "ㄙㄠ") - varLineData.selfReplace("sei", "ㄙㄟ") - varLineData.selfReplace("sen", "ㄙㄣ") - varLineData.selfReplace("sha", "ㄕㄚ") - varLineData.selfReplace("she", "ㄕㄜ") - varLineData.selfReplace("shi", "ㄕ") - varLineData.selfReplace("shu", "ㄕㄨ") - varLineData.selfReplace("sou", "ㄙㄡ") - varLineData.selfReplace("sui", "ㄙㄨㄟ") - varLineData.selfReplace("sun", "ㄙㄨㄣ") - varLineData.selfReplace("suo", "ㄙㄨㄛ") - varLineData.selfReplace("tai", "ㄊㄞ") - varLineData.selfReplace("tan", "ㄊㄢ") - varLineData.selfReplace("tao", "ㄊㄠ") - varLineData.selfReplace("tie", "ㄊㄧㄝ") - varLineData.selfReplace("tou", "ㄊㄡ") - varLineData.selfReplace("tui", "ㄊㄨㄟ") - varLineData.selfReplace("tun", "ㄊㄨㄣ") - varLineData.selfReplace("tuo", "ㄊㄨㄛ") - varLineData.selfReplace("wai", "ㄨㄞ") - varLineData.selfReplace("wan", "ㄨㄢ") - varLineData.selfReplace("wei", "ㄨㄟ") - varLineData.selfReplace("wen", "ㄨㄣ") - varLineData.selfReplace("xia", "ㄒㄧㄚ") - varLineData.selfReplace("xie", "ㄒㄧㄝ") - varLineData.selfReplace("xin", "ㄒㄧㄣ") - varLineData.selfReplace("xiu", "ㄒㄧㄡ") - varLineData.selfReplace("xue", "ㄒㄩㄝ") - varLineData.selfReplace("xun", "ㄒㄩㄣ") - varLineData.selfReplace("yai", "ㄧㄞ") - varLineData.selfReplace("yan", "ㄧㄢ") - varLineData.selfReplace("yao", "ㄧㄠ") - varLineData.selfReplace("yin", "ㄧㄣ") - varLineData.selfReplace("you", "ㄧㄡ") - varLineData.selfReplace("yue", "ㄩㄝ") - varLineData.selfReplace("yun", "ㄩㄣ") - varLineData.selfReplace("zai", "ㄗㄞ") - varLineData.selfReplace("zan", "ㄗㄢ") - varLineData.selfReplace("zao", "ㄗㄠ") - varLineData.selfReplace("zei", "ㄗㄟ") - varLineData.selfReplace("zen", "ㄗㄣ") - varLineData.selfReplace("zha", "ㄓㄚ") - varLineData.selfReplace("zhe", "ㄓㄜ") - varLineData.selfReplace("zhi", "ㄓ") - varLineData.selfReplace("zhu", "ㄓㄨ") - varLineData.selfReplace("zou", "ㄗㄡ") - varLineData.selfReplace("zui", "ㄗㄨㄟ") - varLineData.selfReplace("zun", "ㄗㄨㄣ") - varLineData.selfReplace("zuo", "ㄗㄨㄛ") - varLineData.selfReplace("ai", "ㄞ") - varLineData.selfReplace("an", "ㄢ") - varLineData.selfReplace("ao", "ㄠ") - varLineData.selfReplace("ba", "ㄅㄚ") - varLineData.selfReplace("bi", "ㄅㄧ") - varLineData.selfReplace("bo", "ㄅㄛ") - varLineData.selfReplace("bu", "ㄅㄨ") - varLineData.selfReplace("ca", "ㄘㄚ") - varLineData.selfReplace("ce", "ㄘㄜ") - varLineData.selfReplace("ci", "ㄘ") - varLineData.selfReplace("cu", "ㄘㄨ") - varLineData.selfReplace("da", "ㄉㄚ") - varLineData.selfReplace("de", "ㄉㄜ") - varLineData.selfReplace("di", "ㄉㄧ") - varLineData.selfReplace("du", "ㄉㄨ") - varLineData.selfReplace("eh", "ㄝ") - varLineData.selfReplace("ei", "ㄟ") - varLineData.selfReplace("en", "ㄣ") - varLineData.selfReplace("er", "ㄦ") - varLineData.selfReplace("fa", "ㄈㄚ") - varLineData.selfReplace("fo", "ㄈㄛ") - varLineData.selfReplace("fu", "ㄈㄨ") - varLineData.selfReplace("ga", "ㄍㄚ") - varLineData.selfReplace("ge", "ㄍㄜ") - varLineData.selfReplace("gi", "ㄍㄧ") - varLineData.selfReplace("gu", "ㄍㄨ") - varLineData.selfReplace("ha", "ㄏㄚ") - varLineData.selfReplace("he", "ㄏㄜ") - varLineData.selfReplace("hu", "ㄏㄨ") - varLineData.selfReplace("ji", "ㄐㄧ") - varLineData.selfReplace("ju", "ㄐㄩ") - varLineData.selfReplace("ka", "ㄎㄚ") - varLineData.selfReplace("ke", "ㄎㄜ") - varLineData.selfReplace("ku", "ㄎㄨ") - varLineData.selfReplace("la", "ㄌㄚ") - varLineData.selfReplace("le", "ㄌㄜ") - varLineData.selfReplace("li", "ㄌㄧ") - varLineData.selfReplace("lo", "ㄌㄛ") - varLineData.selfReplace("lu", "ㄌㄨ") - varLineData.selfReplace("lv", "ㄌㄩ") - varLineData.selfReplace("ma", "ㄇㄚ") - varLineData.selfReplace("me", "ㄇㄜ") - varLineData.selfReplace("mi", "ㄇㄧ") - varLineData.selfReplace("mo", "ㄇㄛ") - varLineData.selfReplace("mu", "ㄇㄨ") - varLineData.selfReplace("na", "ㄋㄚ") - varLineData.selfReplace("ne", "ㄋㄜ") - varLineData.selfReplace("ni", "ㄋㄧ") - varLineData.selfReplace("nu", "ㄋㄨ") - varLineData.selfReplace("nv", "ㄋㄩ") - varLineData.selfReplace("ou", "ㄡ") - varLineData.selfReplace("pa", "ㄆㄚ") - varLineData.selfReplace("pi", "ㄆㄧ") - varLineData.selfReplace("po", "ㄆㄛ") - varLineData.selfReplace("pu", "ㄆㄨ") - varLineData.selfReplace("qi", "ㄑㄧ") - varLineData.selfReplace("qu", "ㄑㄩ") - varLineData.selfReplace("re", "ㄖㄜ") - varLineData.selfReplace("ri", "ㄖ") - varLineData.selfReplace("ru", "ㄖㄨ") - varLineData.selfReplace("sa", "ㄙㄚ") - varLineData.selfReplace("se", "ㄙㄜ") - varLineData.selfReplace("si", "ㄙ") - varLineData.selfReplace("su", "ㄙㄨ") - varLineData.selfReplace("ta", "ㄊㄚ") - varLineData.selfReplace("te", "ㄊㄜ") - varLineData.selfReplace("ti", "ㄊㄧ") - varLineData.selfReplace("tu", "ㄊㄨ") - varLineData.selfReplace("wa", "ㄨㄚ") - varLineData.selfReplace("wo", "ㄨㄛ") - varLineData.selfReplace("wu", "ㄨ") - varLineData.selfReplace("xi", "ㄒㄧ") - varLineData.selfReplace("xu", "ㄒㄩ") - varLineData.selfReplace("ya", "ㄧㄚ") - varLineData.selfReplace("ye", "ㄧㄝ") - varLineData.selfReplace("yi", "ㄧ") - varLineData.selfReplace("yo", "ㄧㄛ") - varLineData.selfReplace("yu", "ㄩ") - varLineData.selfReplace("za", "ㄗㄚ") - varLineData.selfReplace("ze", "ㄗㄜ") - varLineData.selfReplace("zi", "ㄗ") - varLineData.selfReplace("zu", "ㄗㄨ") - varLineData.selfReplace("a", "ㄚ") - varLineData.selfReplace("e", "ㄜ") - varLineData.selfReplace("o", "ㄛ") - varLineData.selfReplace("q", "ㄑ") - varLineData.selfReplace("2", "ˊ") - varLineData.selfReplace("3", "ˇ") - varLineData.selfReplace("4", "ˋ") - varLineData.selfReplace("5", "˙") - varLineData.selfReplace("1", "") - strProcessed += varLineData - strProcessed += "\n" - } - } + mutating func formatConsolidate(cnvHYPYtoBPMF: Bool) { + // Step 1: Consolidating formats per line. + var strProcessed = self + // 預處理格式 + strProcessed = strProcessed.replacingOccurrences(of: " #MACOS", with: "") // 去掉 macOS 標記 + // CJKWhiteSpace (\x{3000}) to ASCII Space + // NonBreakWhiteSpace (\x{A0}) to ASCII Space + // Tab to ASCII Space + // 統整連續空格為一個 ASCII 空格 + strProcessed.regReplace(pattern: #"( +| +| +|\t+)+"#, replaceWith: " ") + strProcessed.regReplace(pattern: #"(^ | $)"#, replaceWith: "") // 去除行尾行首空格 + strProcessed.regReplace(pattern: #"(\f+|\r+|\n+)+"#, replaceWith: "\n") // CR & FF to LF, 且去除重複行 + if strProcessed.prefix(1) == " " { // 去除檔案開頭空格 + strProcessed.removeFirst() + } + if strProcessed.suffix(1) == " " { // 去除檔案結尾空格 + strProcessed.removeLast() + } + var arrData = [""] + if cnvHYPYtoBPMF { + // Step 0: Convert HanyuPinyin to Bopomofo. + arrData = strProcessed.components(separatedBy: "\n") + strProcessed = "" // Reset its value + for lineData in arrData { + var varLineData = lineData + // 漢語拼音轉注音,得先從最長的可能的拼音組合開始轉起, + // 這樣等轉換到更短的可能的漢語拼音組合時就不會出錯。 + // 依此類推,聲調放在最後來轉換。 + varLineData.selfReplace("chuang", "ㄔㄨㄤ") + varLineData.selfReplace("shuang", "ㄕㄨㄤ") + varLineData.selfReplace("zhuang", "ㄓㄨㄤ") + varLineData.selfReplace("chang", "ㄔㄤ") + varLineData.selfReplace("cheng", "ㄔㄥ") + varLineData.selfReplace("chong", "ㄔㄨㄥ") + varLineData.selfReplace("chuai", "ㄔㄨㄞ") + varLineData.selfReplace("chuan", "ㄔㄨㄢ") + varLineData.selfReplace("guang", "ㄍㄨㄤ") + varLineData.selfReplace("huang", "ㄏㄨㄤ") + varLineData.selfReplace("jiang", "ㄐㄧㄤ") + varLineData.selfReplace("jiong", "ㄐㄩㄥ") + varLineData.selfReplace("kuang", "ㄎㄨㄤ") + varLineData.selfReplace("liang", "ㄌㄧㄤ") + varLineData.selfReplace("niang", "ㄋㄧㄤ") + varLineData.selfReplace("qiang", "ㄑㄧㄤ") + varLineData.selfReplace("qiong", "ㄑㄩㄥ") + varLineData.selfReplace("shang", "ㄕㄤ") + varLineData.selfReplace("sheng", "ㄕㄥ") + varLineData.selfReplace("shuai", "ㄕㄨㄞ") + varLineData.selfReplace("shuan", "ㄕㄨㄢ") + varLineData.selfReplace("xiang", "ㄒㄧㄤ") + varLineData.selfReplace("xiong", "ㄒㄩㄥ") + varLineData.selfReplace("zhang", "ㄓㄤ") + varLineData.selfReplace("zheng", "ㄓㄥ") + varLineData.selfReplace("zhong", "ㄓㄨㄥ") + varLineData.selfReplace("zhuai", "ㄓㄨㄞ") + varLineData.selfReplace("zhuan", "ㄓㄨㄢ") + varLineData.selfReplace("bang", "ㄅㄤ") + varLineData.selfReplace("beng", "ㄅㄥ") + varLineData.selfReplace("bian", "ㄅㄧㄢ") + varLineData.selfReplace("biao", "ㄅㄧㄠ") + varLineData.selfReplace("bing", "ㄅㄧㄥ") + varLineData.selfReplace("cang", "ㄘㄤ") + varLineData.selfReplace("ceng", "ㄘㄥ") + varLineData.selfReplace("chai", "ㄔㄞ") + varLineData.selfReplace("chan", "ㄔㄢ") + varLineData.selfReplace("chao", "ㄔㄠ") + varLineData.selfReplace("chen", "ㄔㄣ") + varLineData.selfReplace("chou", "ㄔㄡ") + varLineData.selfReplace("chua", "ㄔㄨㄚ") + varLineData.selfReplace("chui", "ㄔㄨㄟ") + varLineData.selfReplace("chun", "ㄔㄨㄣ") + varLineData.selfReplace("chuo", "ㄔㄨㄛ") + varLineData.selfReplace("cong", "ㄘㄨㄥ") + varLineData.selfReplace("cuan", "ㄘㄨㄢ") + varLineData.selfReplace("dang", "ㄉㄤ") + varLineData.selfReplace("deng", "ㄉㄥ") + varLineData.selfReplace("dian", "ㄉㄧㄢ") + varLineData.selfReplace("diao", "ㄉㄧㄠ") + varLineData.selfReplace("ding", "ㄉㄧㄥ") + varLineData.selfReplace("dong", "ㄉㄨㄥ") + varLineData.selfReplace("duan", "ㄉㄨㄢ") + varLineData.selfReplace("fang", "ㄈㄤ") + varLineData.selfReplace("feng", "ㄈㄥ") + varLineData.selfReplace("fiao", "ㄈㄧㄠ") + varLineData.selfReplace("fong", "ㄈㄨㄥ") + varLineData.selfReplace("gang", "ㄍㄤ") + varLineData.selfReplace("geng", "ㄍㄥ") + varLineData.selfReplace("giao", "ㄍㄧㄠ") + varLineData.selfReplace("gong", "ㄍㄨㄥ") + varLineData.selfReplace("guai", "ㄍㄨㄞ") + varLineData.selfReplace("guan", "ㄍㄨㄢ") + varLineData.selfReplace("hang", "ㄏㄤ") + varLineData.selfReplace("heng", "ㄏㄥ") + varLineData.selfReplace("hong", "ㄏㄨㄥ") + varLineData.selfReplace("huai", "ㄏㄨㄞ") + varLineData.selfReplace("huan", "ㄏㄨㄢ") + varLineData.selfReplace("jian", "ㄐㄧㄢ") + varLineData.selfReplace("jiao", "ㄐㄧㄠ") + varLineData.selfReplace("jing", "ㄐㄧㄥ") + varLineData.selfReplace("juan", "ㄐㄩㄢ") + varLineData.selfReplace("kang", "ㄎㄤ") + varLineData.selfReplace("keng", "ㄎㄥ") + varLineData.selfReplace("kong", "ㄎㄨㄥ") + varLineData.selfReplace("kuai", "ㄎㄨㄞ") + varLineData.selfReplace("kuan", "ㄎㄨㄢ") + varLineData.selfReplace("lang", "ㄌㄤ") + varLineData.selfReplace("leng", "ㄌㄥ") + varLineData.selfReplace("lian", "ㄌㄧㄢ") + varLineData.selfReplace("liao", "ㄌㄧㄠ") + varLineData.selfReplace("ling", "ㄌㄧㄥ") + varLineData.selfReplace("long", "ㄌㄨㄥ") + varLineData.selfReplace("luan", "ㄌㄨㄢ") + varLineData.selfReplace("lvan", "ㄌㄩㄢ") + varLineData.selfReplace("mang", "ㄇㄤ") + varLineData.selfReplace("meng", "ㄇㄥ") + varLineData.selfReplace("mian", "ㄇㄧㄢ") + varLineData.selfReplace("miao", "ㄇㄧㄠ") + varLineData.selfReplace("ming", "ㄇㄧㄥ") + varLineData.selfReplace("nang", "ㄋㄤ") + varLineData.selfReplace("neng", "ㄋㄥ") + varLineData.selfReplace("nian", "ㄋㄧㄢ") + varLineData.selfReplace("niao", "ㄋㄧㄠ") + varLineData.selfReplace("ning", "ㄋㄧㄥ") + varLineData.selfReplace("nong", "ㄋㄨㄥ") + varLineData.selfReplace("nuan", "ㄋㄨㄢ") + varLineData.selfReplace("pang", "ㄆㄤ") + varLineData.selfReplace("peng", "ㄆㄥ") + varLineData.selfReplace("pian", "ㄆㄧㄢ") + varLineData.selfReplace("piao", "ㄆㄧㄠ") + varLineData.selfReplace("ping", "ㄆㄧㄥ") + varLineData.selfReplace("qian", "ㄑㄧㄢ") + varLineData.selfReplace("qiao", "ㄑㄧㄠ") + varLineData.selfReplace("qing", "ㄑㄧㄥ") + varLineData.selfReplace("quan", "ㄑㄩㄢ") + varLineData.selfReplace("rang", "ㄖㄤ") + varLineData.selfReplace("reng", "ㄖㄥ") + varLineData.selfReplace("rong", "ㄖㄨㄥ") + varLineData.selfReplace("ruan", "ㄖㄨㄢ") + varLineData.selfReplace("sang", "ㄙㄤ") + varLineData.selfReplace("seng", "ㄙㄥ") + varLineData.selfReplace("shai", "ㄕㄞ") + varLineData.selfReplace("shan", "ㄕㄢ") + varLineData.selfReplace("shao", "ㄕㄠ") + varLineData.selfReplace("shei", "ㄕㄟ") + varLineData.selfReplace("shen", "ㄕㄣ") + varLineData.selfReplace("shou", "ㄕㄡ") + varLineData.selfReplace("shua", "ㄕㄨㄚ") + varLineData.selfReplace("shui", "ㄕㄨㄟ") + varLineData.selfReplace("shun", "ㄕㄨㄣ") + varLineData.selfReplace("shuo", "ㄕㄨㄛ") + varLineData.selfReplace("song", "ㄙㄨㄥ") + varLineData.selfReplace("suan", "ㄙㄨㄢ") + varLineData.selfReplace("tang", "ㄊㄤ") + varLineData.selfReplace("teng", "ㄊㄥ") + varLineData.selfReplace("tian", "ㄊㄧㄢ") + varLineData.selfReplace("tiao", "ㄊㄧㄠ") + varLineData.selfReplace("ting", "ㄊㄧㄥ") + varLineData.selfReplace("tong", "ㄊㄨㄥ") + varLineData.selfReplace("tuan", "ㄊㄨㄢ") + varLineData.selfReplace("wang", "ㄨㄤ") + varLineData.selfReplace("weng", "ㄨㄥ") + varLineData.selfReplace("xian", "ㄒㄧㄢ") + varLineData.selfReplace("xiao", "ㄒㄧㄠ") + varLineData.selfReplace("xing", "ㄒㄧㄥ") + varLineData.selfReplace("xuan", "ㄒㄩㄢ") + varLineData.selfReplace("yang", "ㄧㄤ") + varLineData.selfReplace("ying", "ㄧㄥ") + varLineData.selfReplace("yong", "ㄩㄥ") + varLineData.selfReplace("yuan", "ㄩㄢ") + varLineData.selfReplace("zang", "ㄗㄤ") + varLineData.selfReplace("zeng", "ㄗㄥ") + varLineData.selfReplace("zhai", "ㄓㄞ") + varLineData.selfReplace("zhan", "ㄓㄢ") + varLineData.selfReplace("zhao", "ㄓㄠ") + varLineData.selfReplace("zhei", "ㄓㄟ") + varLineData.selfReplace("zhen", "ㄓㄣ") + varLineData.selfReplace("zhou", "ㄓㄡ") + varLineData.selfReplace("zhua", "ㄓㄨㄚ") + varLineData.selfReplace("zhui", "ㄓㄨㄟ") + varLineData.selfReplace("zhun", "ㄓㄨㄣ") + varLineData.selfReplace("zhuo", "ㄓㄨㄛ") + varLineData.selfReplace("zong", "ㄗㄨㄥ") + varLineData.selfReplace("zuan", "ㄗㄨㄢ") + varLineData.selfReplace("jun", "ㄐㄩㄣ") + varLineData.selfReplace("ang", "ㄤ") + varLineData.selfReplace("bai", "ㄅㄞ") + varLineData.selfReplace("ban", "ㄅㄢ") + varLineData.selfReplace("bao", "ㄅㄠ") + varLineData.selfReplace("bei", "ㄅㄟ") + varLineData.selfReplace("ben", "ㄅㄣ") + varLineData.selfReplace("bie", "ㄅㄧㄝ") + varLineData.selfReplace("bin", "ㄅㄧㄣ") + varLineData.selfReplace("cai", "ㄘㄞ") + varLineData.selfReplace("can", "ㄘㄢ") + varLineData.selfReplace("cao", "ㄘㄠ") + varLineData.selfReplace("cei", "ㄘㄟ") + varLineData.selfReplace("cen", "ㄘㄣ") + varLineData.selfReplace("cha", "ㄔㄚ") + varLineData.selfReplace("che", "ㄔㄜ") + varLineData.selfReplace("chi", "ㄔ") + varLineData.selfReplace("chu", "ㄔㄨ") + varLineData.selfReplace("cou", "ㄘㄡ") + varLineData.selfReplace("cui", "ㄘㄨㄟ") + varLineData.selfReplace("cun", "ㄘㄨㄣ") + varLineData.selfReplace("cuo", "ㄘㄨㄛ") + varLineData.selfReplace("dai", "ㄉㄞ") + varLineData.selfReplace("dan", "ㄉㄢ") + varLineData.selfReplace("dao", "ㄉㄠ") + varLineData.selfReplace("dei", "ㄉㄟ") + varLineData.selfReplace("den", "ㄉㄣ") + varLineData.selfReplace("dia", "ㄉㄧㄚ") + varLineData.selfReplace("die", "ㄉㄧㄝ") + varLineData.selfReplace("diu", "ㄉㄧㄡ") + varLineData.selfReplace("dou", "ㄉㄡ") + varLineData.selfReplace("dui", "ㄉㄨㄟ") + varLineData.selfReplace("dun", "ㄉㄨㄣ") + varLineData.selfReplace("duo", "ㄉㄨㄛ") + varLineData.selfReplace("eng", "ㄥ") + varLineData.selfReplace("fan", "ㄈㄢ") + varLineData.selfReplace("fei", "ㄈㄟ") + varLineData.selfReplace("fen", "ㄈㄣ") + varLineData.selfReplace("fou", "ㄈㄡ") + varLineData.selfReplace("gai", "ㄍㄞ") + varLineData.selfReplace("gan", "ㄍㄢ") + varLineData.selfReplace("gao", "ㄍㄠ") + varLineData.selfReplace("gei", "ㄍㄟ") + varLineData.selfReplace("gin", "ㄍㄧㄣ") + varLineData.selfReplace("gen", "ㄍㄣ") + varLineData.selfReplace("gou", "ㄍㄡ") + varLineData.selfReplace("gua", "ㄍㄨㄚ") + varLineData.selfReplace("gue", "ㄍㄨㄜ") + varLineData.selfReplace("gui", "ㄍㄨㄟ") + varLineData.selfReplace("gun", "ㄍㄨㄣ") + varLineData.selfReplace("guo", "ㄍㄨㄛ") + varLineData.selfReplace("hai", "ㄏㄞ") + varLineData.selfReplace("han", "ㄏㄢ") + varLineData.selfReplace("hao", "ㄏㄠ") + varLineData.selfReplace("hei", "ㄏㄟ") + varLineData.selfReplace("hen", "ㄏㄣ") + varLineData.selfReplace("hou", "ㄏㄡ") + varLineData.selfReplace("hua", "ㄏㄨㄚ") + varLineData.selfReplace("hui", "ㄏㄨㄟ") + varLineData.selfReplace("hun", "ㄏㄨㄣ") + varLineData.selfReplace("huo", "ㄏㄨㄛ") + varLineData.selfReplace("jia", "ㄐㄧㄚ") + varLineData.selfReplace("jie", "ㄐㄧㄝ") + varLineData.selfReplace("jin", "ㄐㄧㄣ") + varLineData.selfReplace("jiu", "ㄐㄧㄡ") + varLineData.selfReplace("jue", "ㄐㄩㄝ") + varLineData.selfReplace("kai", "ㄎㄞ") + varLineData.selfReplace("kan", "ㄎㄢ") + varLineData.selfReplace("kao", "ㄎㄠ") + varLineData.selfReplace("ken", "ㄎㄣ") + varLineData.selfReplace("kiu", "ㄎㄧㄡ") + varLineData.selfReplace("kou", "ㄎㄡ") + varLineData.selfReplace("kua", "ㄎㄨㄚ") + varLineData.selfReplace("kui", "ㄎㄨㄟ") + varLineData.selfReplace("kun", "ㄎㄨㄣ") + varLineData.selfReplace("kuo", "ㄎㄨㄛ") + varLineData.selfReplace("lai", "ㄌㄞ") + varLineData.selfReplace("lan", "ㄌㄢ") + varLineData.selfReplace("lao", "ㄌㄠ") + varLineData.selfReplace("lei", "ㄌㄟ") + varLineData.selfReplace("lia", "ㄌㄧㄚ") + varLineData.selfReplace("lie", "ㄌㄧㄝ") + varLineData.selfReplace("lin", "ㄌㄧㄣ") + varLineData.selfReplace("liu", "ㄌㄧㄡ") + varLineData.selfReplace("lou", "ㄌㄡ") + varLineData.selfReplace("lun", "ㄌㄨㄣ") + varLineData.selfReplace("luo", "ㄌㄨㄛ") + varLineData.selfReplace("lve", "ㄌㄩㄝ") + varLineData.selfReplace("mai", "ㄇㄞ") + varLineData.selfReplace("man", "ㄇㄢ") + varLineData.selfReplace("mao", "ㄇㄠ") + varLineData.selfReplace("mei", "ㄇㄟ") + varLineData.selfReplace("men", "ㄇㄣ") + varLineData.selfReplace("mie", "ㄇㄧㄝ") + varLineData.selfReplace("min", "ㄇㄧㄣ") + varLineData.selfReplace("miu", "ㄇㄧㄡ") + varLineData.selfReplace("mou", "ㄇㄡ") + varLineData.selfReplace("nai", "ㄋㄞ") + varLineData.selfReplace("nan", "ㄋㄢ") + varLineData.selfReplace("nao", "ㄋㄠ") + varLineData.selfReplace("nei", "ㄋㄟ") + varLineData.selfReplace("nen", "ㄋㄣ") + varLineData.selfReplace("nie", "ㄋㄧㄝ") + varLineData.selfReplace("nin", "ㄋㄧㄣ") + varLineData.selfReplace("niu", "ㄋㄧㄡ") + varLineData.selfReplace("nou", "ㄋㄡ") + varLineData.selfReplace("nui", "ㄋㄨㄟ") + varLineData.selfReplace("nun", "ㄋㄨㄣ") + varLineData.selfReplace("nuo", "ㄋㄨㄛ") + varLineData.selfReplace("nve", "ㄋㄩㄝ") + varLineData.selfReplace("pai", "ㄆㄞ") + varLineData.selfReplace("pan", "ㄆㄢ") + varLineData.selfReplace("pao", "ㄆㄠ") + varLineData.selfReplace("pei", "ㄆㄟ") + varLineData.selfReplace("pen", "ㄆㄣ") + varLineData.selfReplace("pia", "ㄆㄧㄚ") + varLineData.selfReplace("pie", "ㄆㄧㄝ") + varLineData.selfReplace("pin", "ㄆㄧㄣ") + varLineData.selfReplace("pou", "ㄆㄡ") + varLineData.selfReplace("qia", "ㄑㄧㄚ") + varLineData.selfReplace("qie", "ㄑㄧㄝ") + varLineData.selfReplace("qin", "ㄑㄧㄣ") + varLineData.selfReplace("qiu", "ㄑㄧㄡ") + varLineData.selfReplace("que", "ㄑㄩㄝ") + varLineData.selfReplace("qun", "ㄑㄩㄣ") + varLineData.selfReplace("ran", "ㄖㄢ") + varLineData.selfReplace("rao", "ㄖㄠ") + varLineData.selfReplace("ren", "ㄖㄣ") + varLineData.selfReplace("rou", "ㄖㄡ") + varLineData.selfReplace("rui", "ㄖㄨㄟ") + varLineData.selfReplace("run", "ㄖㄨㄣ") + varLineData.selfReplace("ruo", "ㄖㄨㄛ") + varLineData.selfReplace("sai", "ㄙㄞ") + varLineData.selfReplace("san", "ㄙㄢ") + varLineData.selfReplace("sao", "ㄙㄠ") + varLineData.selfReplace("sei", "ㄙㄟ") + varLineData.selfReplace("sen", "ㄙㄣ") + varLineData.selfReplace("sha", "ㄕㄚ") + varLineData.selfReplace("she", "ㄕㄜ") + varLineData.selfReplace("shi", "ㄕ") + varLineData.selfReplace("shu", "ㄕㄨ") + varLineData.selfReplace("sou", "ㄙㄡ") + varLineData.selfReplace("sui", "ㄙㄨㄟ") + varLineData.selfReplace("sun", "ㄙㄨㄣ") + varLineData.selfReplace("suo", "ㄙㄨㄛ") + varLineData.selfReplace("tai", "ㄊㄞ") + varLineData.selfReplace("tan", "ㄊㄢ") + varLineData.selfReplace("tao", "ㄊㄠ") + varLineData.selfReplace("tie", "ㄊㄧㄝ") + varLineData.selfReplace("tou", "ㄊㄡ") + varLineData.selfReplace("tui", "ㄊㄨㄟ") + varLineData.selfReplace("tun", "ㄊㄨㄣ") + varLineData.selfReplace("tuo", "ㄊㄨㄛ") + varLineData.selfReplace("wai", "ㄨㄞ") + varLineData.selfReplace("wan", "ㄨㄢ") + varLineData.selfReplace("wei", "ㄨㄟ") + varLineData.selfReplace("wen", "ㄨㄣ") + varLineData.selfReplace("xia", "ㄒㄧㄚ") + varLineData.selfReplace("xie", "ㄒㄧㄝ") + varLineData.selfReplace("xin", "ㄒㄧㄣ") + varLineData.selfReplace("xiu", "ㄒㄧㄡ") + varLineData.selfReplace("xue", "ㄒㄩㄝ") + varLineData.selfReplace("xun", "ㄒㄩㄣ") + varLineData.selfReplace("yai", "ㄧㄞ") + varLineData.selfReplace("yan", "ㄧㄢ") + varLineData.selfReplace("yao", "ㄧㄠ") + varLineData.selfReplace("yin", "ㄧㄣ") + varLineData.selfReplace("you", "ㄧㄡ") + varLineData.selfReplace("yue", "ㄩㄝ") + varLineData.selfReplace("yun", "ㄩㄣ") + varLineData.selfReplace("zai", "ㄗㄞ") + varLineData.selfReplace("zan", "ㄗㄢ") + varLineData.selfReplace("zao", "ㄗㄠ") + varLineData.selfReplace("zei", "ㄗㄟ") + varLineData.selfReplace("zen", "ㄗㄣ") + varLineData.selfReplace("zha", "ㄓㄚ") + varLineData.selfReplace("zhe", "ㄓㄜ") + varLineData.selfReplace("zhi", "ㄓ") + varLineData.selfReplace("zhu", "ㄓㄨ") + varLineData.selfReplace("zou", "ㄗㄡ") + varLineData.selfReplace("zui", "ㄗㄨㄟ") + varLineData.selfReplace("zun", "ㄗㄨㄣ") + varLineData.selfReplace("zuo", "ㄗㄨㄛ") + varLineData.selfReplace("ai", "ㄞ") + varLineData.selfReplace("an", "ㄢ") + varLineData.selfReplace("ao", "ㄠ") + varLineData.selfReplace("ba", "ㄅㄚ") + varLineData.selfReplace("bi", "ㄅㄧ") + varLineData.selfReplace("bo", "ㄅㄛ") + varLineData.selfReplace("bu", "ㄅㄨ") + varLineData.selfReplace("ca", "ㄘㄚ") + varLineData.selfReplace("ce", "ㄘㄜ") + varLineData.selfReplace("ci", "ㄘ") + varLineData.selfReplace("cu", "ㄘㄨ") + varLineData.selfReplace("da", "ㄉㄚ") + varLineData.selfReplace("de", "ㄉㄜ") + varLineData.selfReplace("di", "ㄉㄧ") + varLineData.selfReplace("du", "ㄉㄨ") + varLineData.selfReplace("eh", "ㄝ") + varLineData.selfReplace("ei", "ㄟ") + varLineData.selfReplace("en", "ㄣ") + varLineData.selfReplace("er", "ㄦ") + varLineData.selfReplace("fa", "ㄈㄚ") + varLineData.selfReplace("fo", "ㄈㄛ") + varLineData.selfReplace("fu", "ㄈㄨ") + varLineData.selfReplace("ga", "ㄍㄚ") + varLineData.selfReplace("ge", "ㄍㄜ") + varLineData.selfReplace("gi", "ㄍㄧ") + varLineData.selfReplace("gu", "ㄍㄨ") + varLineData.selfReplace("ha", "ㄏㄚ") + varLineData.selfReplace("he", "ㄏㄜ") + varLineData.selfReplace("hu", "ㄏㄨ") + varLineData.selfReplace("ji", "ㄐㄧ") + varLineData.selfReplace("ju", "ㄐㄩ") + varLineData.selfReplace("ka", "ㄎㄚ") + varLineData.selfReplace("ke", "ㄎㄜ") + varLineData.selfReplace("ku", "ㄎㄨ") + varLineData.selfReplace("la", "ㄌㄚ") + varLineData.selfReplace("le", "ㄌㄜ") + varLineData.selfReplace("li", "ㄌㄧ") + varLineData.selfReplace("lo", "ㄌㄛ") + varLineData.selfReplace("lu", "ㄌㄨ") + varLineData.selfReplace("lv", "ㄌㄩ") + varLineData.selfReplace("ma", "ㄇㄚ") + varLineData.selfReplace("me", "ㄇㄜ") + varLineData.selfReplace("mi", "ㄇㄧ") + varLineData.selfReplace("mo", "ㄇㄛ") + varLineData.selfReplace("mu", "ㄇㄨ") + varLineData.selfReplace("na", "ㄋㄚ") + varLineData.selfReplace("ne", "ㄋㄜ") + varLineData.selfReplace("ni", "ㄋㄧ") + varLineData.selfReplace("nu", "ㄋㄨ") + varLineData.selfReplace("nv", "ㄋㄩ") + varLineData.selfReplace("ou", "ㄡ") + varLineData.selfReplace("pa", "ㄆㄚ") + varLineData.selfReplace("pi", "ㄆㄧ") + varLineData.selfReplace("po", "ㄆㄛ") + varLineData.selfReplace("pu", "ㄆㄨ") + varLineData.selfReplace("qi", "ㄑㄧ") + varLineData.selfReplace("qu", "ㄑㄩ") + varLineData.selfReplace("re", "ㄖㄜ") + varLineData.selfReplace("ri", "ㄖ") + varLineData.selfReplace("ru", "ㄖㄨ") + varLineData.selfReplace("sa", "ㄙㄚ") + varLineData.selfReplace("se", "ㄙㄜ") + varLineData.selfReplace("si", "ㄙ") + varLineData.selfReplace("su", "ㄙㄨ") + varLineData.selfReplace("ta", "ㄊㄚ") + varLineData.selfReplace("te", "ㄊㄜ") + varLineData.selfReplace("ti", "ㄊㄧ") + varLineData.selfReplace("tu", "ㄊㄨ") + varLineData.selfReplace("wa", "ㄨㄚ") + varLineData.selfReplace("wo", "ㄨㄛ") + varLineData.selfReplace("wu", "ㄨ") + varLineData.selfReplace("xi", "ㄒㄧ") + varLineData.selfReplace("xu", "ㄒㄩ") + varLineData.selfReplace("ya", "ㄧㄚ") + varLineData.selfReplace("ye", "ㄧㄝ") + varLineData.selfReplace("yi", "ㄧ") + varLineData.selfReplace("yo", "ㄧㄛ") + varLineData.selfReplace("yu", "ㄩ") + varLineData.selfReplace("za", "ㄗㄚ") + varLineData.selfReplace("ze", "ㄗㄜ") + varLineData.selfReplace("zi", "ㄗ") + varLineData.selfReplace("zu", "ㄗㄨ") + varLineData.selfReplace("a", "ㄚ") + varLineData.selfReplace("e", "ㄜ") + varLineData.selfReplace("o", "ㄛ") + varLineData.selfReplace("q", "ㄑ") + varLineData.selfReplace("2", "ˊ") + varLineData.selfReplace("3", "ˇ") + varLineData.selfReplace("4", "ˋ") + varLineData.selfReplace("5", "˙") + varLineData.selfReplace("1", "") + strProcessed += varLineData + strProcessed += "\n" + } + } - // Step 3: Add Formatted Pragma, the Sorted Header: - let hdrFormatted = "# 𝙵𝙾𝚁𝙼𝙰𝚃 𝚘𝚛𝚐.𝚊𝚝𝚎𝚕𝚒𝚎𝚛𝙸𝚗𝚖𝚞.𝚟𝚌𝚑𝚎𝚠𝚒𝚗𝚐.𝚞𝚜𝚎𝚛𝙻𝚊𝚗𝚐𝚞𝚊𝚐𝚎𝙼𝚘𝚍𝚎𝚕𝙳𝚊𝚝𝚊.𝚏𝚘𝚛𝚖𝚊𝚝𝚝𝚎𝚍\n" - strProcessed = hdrFormatted + strProcessed // Add Sorted Header + // Step 3: Add Formatted Pragma, the Sorted Header: + let hdrFormatted = "# 𝙵𝙾𝚁𝙼𝙰𝚃 𝚘𝚛𝚐.𝚊𝚝𝚎𝚕𝚒𝚎𝚛𝙸𝚗𝚖𝚞.𝚟𝚌𝚑𝚎𝚠𝚒𝚗𝚐.𝚞𝚜𝚎𝚛𝙻𝚊𝚗𝚐𝚞𝚊𝚐𝚎𝙼𝚘𝚍𝚎𝚕𝙳𝚊𝚝𝚊.𝚏𝚘𝚛𝚖𝚊𝚝𝚝𝚎𝚍\n" + strProcessed = hdrFormatted + strProcessed // Add Sorted Header - // Step 4: Deduplication. - arrData = strProcessed.components(separatedBy: "\n") - strProcessed = "" // Reset its value - // 下面兩行的 reversed 是首尾顛倒,免得破壞最新的 override 資訊。 - let arrDataDeduplicated = Array(NSOrderedSet(array: arrData.reversed()).array as! [String]) - for lineData in arrDataDeduplicated.reversed() { - strProcessed += lineData - strProcessed += "\n" - } + // Step 4: Deduplication. + arrData = strProcessed.components(separatedBy: "\n") + strProcessed = "" // Reset its value + // 下面兩行的 reversed 是首尾顛倒,免得破壞最新的 override 資訊。 + let arrDataDeduplicated = Array(NSOrderedSet(array: arrData.reversed()).array as! [String]) + for lineData in arrDataDeduplicated.reversed() { + strProcessed += lineData + strProcessed += "\n" + } - // Step 5: Remove duplicated newlines at the end of the file. - strProcessed.regReplace(pattern: "\\n+", replaceWith: "\n") + // Step 5: Remove duplicated newlines at the end of the file. + strProcessed.regReplace(pattern: "\\n+", replaceWith: "\n") - // Step 6: Commit Formatted Contents. - self = strProcessed - } + // Step 6: Commit Formatted Contents. + self = strProcessed + } } diff --git a/UserPhraseEditor/ViewController.swift b/UserPhraseEditor/ViewController.swift index ef04c6c0..ddaed512 100644 --- a/UserPhraseEditor/ViewController.swift +++ b/UserPhraseEditor/ViewController.swift @@ -25,41 +25,41 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import Cocoa class ViewController: NSViewController, NSTextViewDelegate { - @IBOutlet var edtDocument: NSTextView! - /// - Tag: setRepresentedObjectExample - override var representedObject: Any? { - didSet { - // Pass down the represented object to all of the child view controllers. - for child in children { - child.representedObject = representedObject - } - } - } + @IBOutlet var edtDocument: NSTextView! + /// - Tag: setRepresentedObjectExample + override var representedObject: Any? { + didSet { + // Pass down the represented object to all of the child view controllers. + for child in children { + child.representedObject = representedObject + } + } + } - weak var document: Document? { - if let docRepresentedObject = representedObject as? Document { - return docRepresentedObject - } - return nil - } + weak var document: Document? { + if let docRepresentedObject = representedObject as? Document { + return docRepresentedObject + } + return nil + } - override func viewDidLoad() { - super.viewDidLoad() - // Do any additional setup after loading the view. - edtDocument.font = NSFont(name: "Monaco", size: 16) - } + override func viewDidLoad() { + super.viewDidLoad() + // Do any additional setup after loading the view. + edtDocument.font = NSFont(name: "Monaco", size: 16) + } - override func viewDidAppear() { - super.viewDidAppear() - } + override func viewDidAppear() { + super.viewDidAppear() + } - // MARK: - NSTextViewDelegate + // MARK: - NSTextViewDelegate - func textDidBeginEditing(_: Notification) { - document?.objectDidBeginEditing(self) - } + func textDidBeginEditing(_: Notification) { + document?.objectDidBeginEditing(self) + } - func textDidEndEditing(_: Notification) { - document?.objectDidEndEditing(self) - } + func textDidEndEditing(_: Notification) { + document?.objectDidEndEditing(self) + } } diff --git a/UserPhraseEditor/WindowController.swift b/UserPhraseEditor/WindowController.swift index 7af24322..ae306f32 100644 --- a/UserPhraseEditor/WindowController.swift +++ b/UserPhraseEditor/WindowController.swift @@ -25,15 +25,12 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import Cocoa class WindowController: NSWindowController, NSWindowDelegate { - override func windowDidLoad() { - super.windowDidLoad() - } + override func windowDidLoad() { + super.windowDidLoad() + } - required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - /** NSWindows loaded from the storyboard will be cascaded - based on the original frame of the window in the storyboard. - */ - shouldCascadeWindows = true - } + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + shouldCascadeWindows = true + } } diff --git a/UserPhraseEditor/ctlAboutWindow.swift b/UserPhraseEditor/ctlAboutWindow.swift index 54ca1d16..c4c9fee4 100644 --- a/UserPhraseEditor/ctlAboutWindow.swift +++ b/UserPhraseEditor/ctlAboutWindow.swift @@ -27,39 +27,39 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import Cocoa @objc(AboutWindow) class ctlAboutWindow: NSWindowController { - @IBOutlet var appVersionLabel: NSTextField! - @IBOutlet var appCopyrightLabel: NSTextField! - @IBOutlet var appEULAContent: NSTextView! + @IBOutlet var appVersionLabel: NSTextField! + @IBOutlet var appCopyrightLabel: NSTextField! + @IBOutlet var appEULAContent: NSTextView! - override func windowDidLoad() { - super.windowDidLoad() + override func windowDidLoad() { + super.windowDidLoad() - window?.standardWindowButton(.closeButton)?.isHidden = true - window?.standardWindowButton(.miniaturizeButton)?.isHidden = true - window?.standardWindowButton(.zoomButton)?.isHidden = true - guard - let installingVersion = Bundle.main.infoDictionary?[kCFBundleVersionKey as String] - as? String, - let versionString = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String - else { - return - } - if let copyrightLabel = Bundle.main.localizedInfoDictionary?["NSHumanReadableCopyright"] - as? String - { - appCopyrightLabel.stringValue = copyrightLabel - } - if let eulaContent = Bundle.main.localizedInfoDictionary?["CFEULAContent"] as? String { - appEULAContent.string = eulaContent - } - appVersionLabel.stringValue = String( - format: "%@ Build %@", versionString, installingVersion - ) - } + window?.standardWindowButton(.closeButton)?.isHidden = true + window?.standardWindowButton(.miniaturizeButton)?.isHidden = true + window?.standardWindowButton(.zoomButton)?.isHidden = true + guard + let installingVersion = Bundle.main.infoDictionary?[kCFBundleVersionKey as String] + as? String, + let versionString = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String + else { + return + } + if let copyrightLabel = Bundle.main.localizedInfoDictionary?["NSHumanReadableCopyright"] + as? String + { + appCopyrightLabel.stringValue = copyrightLabel + } + if let eulaContent = Bundle.main.localizedInfoDictionary?["CFEULAContent"] as? String { + appEULAContent.string = eulaContent + } + appVersionLabel.stringValue = String( + format: "%@ Build %@", versionString, installingVersion + ) + } - @IBAction func btnWiki(_: NSButton) { - if let url = URL(string: "https://gitee.com/vchewing/vChewing-macOS/wikis") { - NSWorkspace.shared.open(url) - } - } + @IBAction func btnWiki(_: NSButton) { + if let url = URL(string: "https://gitee.com/vchewing/vChewing-macOS/wikis") { + NSWorkspace.shared.open(url) + } + } } diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index f97b5b51..c26107d8 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 5B00A230282011980058E5DB /* lmLite.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B00A22F282011980058E5DB /* lmLite.swift */; }; 5B0AF8B527B2C8290096FE54 /* StringExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B0AF8B427B2C8290096FE54 /* StringExtension.swift */; }; 5B11328927B94CFB00E58451 /* AppleKeyboardConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B11328827B94CFB00E58451 /* AppleKeyboardConverter.swift */; }; 5B27AD6A27CB1F9B000ED75B /* data-symbols.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5B27AD6827CB1F9B000ED75B /* data-symbols.txt */; }; @@ -14,10 +15,23 @@ 5B2DB16F27AF6891006D874E /* data-chs.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5B2DB16D27AF6891006D874E /* data-chs.txt */; }; 5B2DB17027AF6891006D874E /* data-cht.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5B2DB16E27AF6891006D874E /* data-cht.txt */; }; 5B3133BF280B229700A4A505 /* KeyHandler_States.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B3133BE280B229700A4A505 /* KeyHandler_States.swift */; }; + 5B38F59A281E2E49007D5F5D /* 6_Unigram.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1D15FC0EB100ABF4B3 /* 6_Unigram.swift */; }; + 5B38F59B281E2E49007D5F5D /* 7_KeyValuePair.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1815FC0EB100ABF4B3 /* 7_KeyValuePair.swift */; }; + 5B38F59C281E2E49007D5F5D /* 2_Grid.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1715FC0EB100ABF4B3 /* 2_Grid.swift */; }; + 5B38F59D281E2E49007D5F5D /* 4_Node.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1A15FC0EB100ABF4B3 /* 4_Node.swift */; }; + 5B38F59E281E2E49007D5F5D /* 6_Bigram.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1415FC0EB100ABF4B3 /* 6_Bigram.swift */; }; + 5B38F59F281E2E49007D5F5D /* 3_NodeAnchor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1B15FC0EB100ABF4B3 /* 3_NodeAnchor.swift */; }; + 5B38F5A0281E2E49007D5F5D /* 1_Walker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1E15FC0EB100ABF4B3 /* 1_Walker.swift */; }; + 5B38F5A1281E2E49007D5F5D /* 1_BlockReadingBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1515FC0EB100ABF4B3 /* 1_BlockReadingBuilder.swift */; }; + 5B38F5A2281E2E49007D5F5D /* 0_Megrez.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1615FC0EB100ABF4B3 /* 0_Megrez.swift */; }; + 5B38F5A3281E2E49007D5F5D /* 3_Span.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1C15FC0EB100ABF4B3 /* 3_Span.swift */; }; + 5B38F5A4281E2E49007D5F5D /* 5_LanguageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1915FC0EB100ABF4B3 /* 5_LanguageModel.swift */; }; + 5B407153281F94E6009C24CB /* Composer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B407152281F94E6009C24CB /* Composer.mm */; }; + 5B40730C281672610023DFFF /* lmAssociates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B407309281672610023DFFF /* lmAssociates.swift */; }; + 5B40730D281672610023DFFF /* lmReplacements.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B40730A281672610023DFFF /* lmReplacements.swift */; }; 5B5E535227EF261400C6AA1E /* IME.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B5E535127EF261400C6AA1E /* IME.swift */; }; 5B61B0CA280BEFD4002E3CFA /* KeyHandler_Misc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B61B0C9280BEFD4002E3CFA /* KeyHandler_Misc.swift */; }; 5B62A32927AE77D100A19448 /* FSEventStreamHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A32827AE77D100A19448 /* FSEventStreamHelper.swift */; }; - 5B62A32F27AE78B000A19448 /* CoreLM.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A32D27AE78B000A19448 /* CoreLM.mm */; }; 5B62A33227AE792F00A19448 /* InputSourceHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A33127AE792F00A19448 /* InputSourceHelper.swift */; }; 5B62A33627AE795800A19448 /* mgrPrefs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A33527AE795800A19448 /* mgrPrefs.swift */; }; 5B62A33827AE79CD00A19448 /* NSStringUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A33727AE79CD00A19448 /* NSStringUtils.swift */; }; @@ -34,6 +48,10 @@ 5B782EC4280C243C007276DE /* KeyHandler_HandleCandidate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B782EC3280C243C007276DE /* KeyHandler_HandleCandidate.swift */; }; 5B7BC4B027AFFBE800F66C24 /* frmPrefWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5B7BC4AE27AFFBE800F66C24 /* frmPrefWindow.xib */; }; 5B7F225D2808501000DDD3CB /* KeyHandler_HandleInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B7F225C2808501000DDD3CB /* KeyHandler_HandleInput.swift */; }; + 5B949BD92816DC5400D87B5D /* LineReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B949BD82816DC5400D87B5D /* LineReader.swift */; }; + 5B949BDB2816DDBC00D87B5D /* LMConsolidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B949BDA2816DDBC00D87B5D /* LMConsolidator.swift */; }; + 5BA0DF312817857D009E73BB /* lmUserOverride.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA0DF2E2817857D009E73BB /* lmUserOverride.swift */; }; + 5BA0DF322817857D009E73BB /* lmCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA0DF2F2817857D009E73BB /* lmCore.swift */; }; 5BA9FD0F27FEDB6B002DE248 /* suiPrefPaneGeneral.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD0A27FEDB6B002DE248 /* suiPrefPaneGeneral.swift */; }; 5BA9FD1027FEDB6B002DE248 /* suiPrefPaneKeyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD0B27FEDB6B002DE248 /* suiPrefPaneKeyboard.swift */; }; 5BA9FD1127FEDB6B002DE248 /* ctlPrefUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD0C27FEDB6B002DE248 /* ctlPrefUI.swift */; }; @@ -66,6 +84,8 @@ 5BBBB77627AED70B0023B93A /* MenuIcon-TCVIM.png in Resources */ = {isa = PBXBuildFile; fileRef = 5BBBB77227AED70B0023B93A /* MenuIcon-TCVIM.png */; }; 5BBBB77A27AEDC690023B93A /* clsSFX.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BBBB77927AEDC690023B93A /* clsSFX.swift */; }; 5BC2652227E04B7E00700291 /* uninstall.sh in Resources */ = {isa = PBXBuildFile; fileRef = 5BC2652127E04B7B00700291 /* uninstall.sh */; }; + 5BD0113B28180D6100609769 /* LMInstantiator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BD0113A28180D6100609769 /* LMInstantiator.swift */; }; + 5BD0113D2818543900609769 /* KeyHandler_Core.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BD0113C2818543900609769 /* KeyHandler_Core.swift */; }; 5BD05B8127B22F3C004C4F1D /* char-kanji-cns.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5BD05B8027B22F3C004C4F1D /* char-kanji-cns.txt */; }; 5BD05BCA27B2A43D004C4F1D /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6A2E40F5253A69DA00D1AE1D /* Images.xcassets */; }; 5BD05C5D27B2BBA9004C4F1D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5BD05C5B27B2BBA9004C4F1D /* Main.storyboard */; }; @@ -78,7 +98,6 @@ 5BDCBB2E27B4E67A00D0CC59 /* vChewingPhraseEditor.app in Resources */ = {isa = PBXBuildFile; fileRef = 5BD05BB827B2A429004C4F1D /* vChewingPhraseEditor.app */; }; 5BE78BD927B3775B005EA1BE /* ctlAboutWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BE78BD827B37750005EA1BE /* ctlAboutWindow.swift */; }; 5BE78BDD27B3776D005EA1BE /* frmAboutWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5BE78BDA27B37764005EA1BE /* frmAboutWindow.xib */; }; - 5BE78BE027B38804005EA1BE /* LMConsolidator.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A32727AE77BB00A19448 /* LMConsolidator.mm */; }; 5BF8423127BAA942008E7E4C /* vChewingKanjiConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BF8423027BAA942008E7E4C /* vChewingKanjiConverter.swift */; }; 6A0D4F4515FC0EB100ABF4B3 /* Mandarin.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F2015FC0EB100ABF4B3 /* Mandarin.cpp */; }; 6A187E2616004C5900466B2E /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6A187E2816004C5900466B2E /* MainMenu.xib */; }; @@ -89,25 +108,16 @@ 6ACA41FC15FC1D9000935EF6 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6ACA41EE15FC1D9000935EF6 /* Localizable.strings */; }; 6ACA41FD15FC1D9000935EF6 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6ACA41F015FC1D9000935EF6 /* MainMenu.xib */; }; 6ACA420215FC1E5200935EF6 /* vChewing.app in Resources */ = {isa = PBXBuildFile; fileRef = 6A0D4EA215FC0D2D00ABF4B3 /* vChewing.app */; }; - 6ACC3D3F27914F2400F1B140 /* KeyValueBlobReader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6ACC3D3E27914F2400F1B140 /* KeyValueBlobReader.cpp */; }; - 6ACC3D442793701600F1B140 /* ParselessPhraseDB.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6ACC3D402793701600F1B140 /* ParselessPhraseDB.cpp */; }; - 6ACC3D452793701600F1B140 /* ParselessLM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6ACC3D422793701600F1B140 /* ParselessLM.cpp */; }; - D41355D8278D74B5005E5CBD /* mgrLangModel.mm in Sources */ = {isa = PBXBuildFile; fileRef = D41355D7278D7409005E5CBD /* mgrLangModel.mm */; }; - D41355DB278E6D17005E5CBD /* LMInstantiator.mm in Sources */ = {isa = PBXBuildFile; fileRef = D41355D9278E6D17005E5CBD /* LMInstantiator.mm */; }; - D41355DE278EA3ED005E5CBD /* UserPhrasesLM.mm in Sources */ = {isa = PBXBuildFile; fileRef = D41355DC278EA3ED005E5CBD /* UserPhrasesLM.mm */; }; D427F76C278CA2B0004A2160 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D427F76B278CA1BA004A2160 /* AppDelegate.swift */; }; - D44FB74D2792189A003C80A6 /* PhraseReplacementMap.mm in Sources */ = {isa = PBXBuildFile; fileRef = D44FB74B2792189A003C80A6 /* PhraseReplacementMap.mm */; }; D456576E279E4F7B00DF6BC9 /* InputHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D456576D279E4F7B00DF6BC9 /* InputHandler.swift */; }; D461B792279DAC010070E734 /* InputState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D461B791279DAC010070E734 /* InputState.swift */; }; D47B92C027972AD100458394 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47B92BF27972AC800458394 /* main.swift */; }; - D47D73AC27A6CAE600255A50 /* AssociatedPhrases.mm in Sources */ = {isa = PBXBuildFile; fileRef = D47D73AA27A6CAE600255A50 /* AssociatedPhrases.mm */; }; D47F7DCE278BFB57002F9DD7 /* ctlPrefWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DCD278BFB57002F9DD7 /* ctlPrefWindow.swift */; }; D47F7DD0278C0897002F9DD7 /* ctlNonModalAlertWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DCF278C0897002F9DD7 /* ctlNonModalAlertWindow.swift */; }; - D47F7DD3278C1263002F9DD7 /* UserOverrideModel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DD2278C1263002F9DD7 /* UserOverrideModel.cpp */; }; D4A13D5A27A59F0B003BE359 /* ctlInputMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4A13D5927A59D5C003BE359 /* ctlInputMethod.swift */; }; D4E33D8A27A838CF006DB1CF /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = D4E33D8827A838CF006DB1CF /* Localizable.strings */; }; D4E33D8F27A838F0006DB1CF /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = D4E33D8D27A838F0006DB1CF /* InfoPlist.strings */; }; - D4E569DC27A34D0E00AC2CEF /* KeyHandler.mm in Sources */ = {isa = PBXBuildFile; fileRef = D4E569DB27A34CC100AC2CEF /* KeyHandler.mm */; }; + D4E569DC27A34D0E00AC2CEF /* CTools.m in Sources */ = {isa = PBXBuildFile; fileRef = D4E569DB27A34CC100AC2CEF /* CTools.m */; }; D4F0BBDF279AF1AF0071253C /* ArchiveUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4F0BBDE279AF1AF0071253C /* ArchiveUtil.swift */; }; D4F0BBE1279AF8B30071253C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4F0BBE0279AF8B30071253C /* AppDelegate.swift */; }; D4F0BBE4279B08900071253C /* Chronosphere.m in Sources */ = {isa = PBXBuildFile; fileRef = D4F0BBE3279B08900071253C /* Chronosphere.m */; }; @@ -151,6 +161,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 5B00A22F282011980058E5DB /* lmLite.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = lmLite.swift; sourceTree = ""; usesTabs = 0; }; 5B04305327B529D800CB65BC /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; 5B04305427B529D800CB65BC /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; 5B04305527B529D800CB65BC /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/MainMenu.strings"; sourceTree = ""; }; @@ -174,9 +185,9 @@ 5B05A47B27AFF7CA00437698 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 5B05A47C27AFF7CF00437698 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; 5B05A47F27AFF84200437698 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/frmAboutWindow.strings; sourceTree = ""; }; - 5B0AF8B427B2C8290096FE54 /* StringExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = StringExtension.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; - 5B0C5EDF27C7D9870078037C /* dataCompiler.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = dataCompiler.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; - 5B11328827B94CFB00E58451 /* AppleKeyboardConverter.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = AppleKeyboardConverter.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; + 5B0AF8B427B2C8290096FE54 /* StringExtension.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = StringExtension.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 5B0C5EDF27C7D9870078037C /* dataCompiler.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = dataCompiler.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 5B11328827B94CFB00E58451 /* AppleKeyboardConverter.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = AppleKeyboardConverter.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; 5B18BA6F27C7BD8B0056EB19 /* LICENSE-CHS.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = "LICENSE-CHS.txt"; sourceTree = ""; }; 5B18BA7027C7BD8B0056EB19 /* Makefile */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = ""; }; 5B18BA7127C7BD8B0056EB19 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; @@ -189,55 +200,56 @@ 5B2DB16E27AF6891006D874E /* data-cht.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "data-cht.txt"; path = "Data/data-cht.txt"; sourceTree = ""; }; 5B2DB17127AF8771006D874E /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; name = Makefile; path = Data/Makefile; sourceTree = ""; }; 5B30F11227BA568800484E24 /* vChewingKeyLayout.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = vChewingKeyLayout.bundle; sourceTree = ""; }; - 5B3133BE280B229700A4A505 /* KeyHandler_States.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = KeyHandler_States.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; - 5B5E535127EF261400C6AA1E /* IME.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = IME.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; - 5B61B0C9280BEFD4002E3CFA /* KeyHandler_Misc.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = KeyHandler_Misc.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; - 5B62A32627AE77BB00A19448 /* LMConsolidator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = LMConsolidator.h; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; - 5B62A32727AE77BB00A19448 /* LMConsolidator.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = LMConsolidator.mm; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; - 5B62A32827AE77D100A19448 /* FSEventStreamHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = FSEventStreamHelper.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; - 5B62A32B27AE78B000A19448 /* CNSLM.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = CNSLM.h; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; - 5B62A32C27AE78B000A19448 /* CoreLM.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = CoreLM.h; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; - 5B62A32D27AE78B000A19448 /* CoreLM.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = CoreLM.mm; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; - 5B62A33127AE792F00A19448 /* InputSourceHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = InputSourceHelper.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; - 5B62A33527AE795800A19448 /* mgrPrefs.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = mgrPrefs.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; - 5B62A33727AE79CD00A19448 /* NSStringUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = NSStringUtils.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; - 5B62A33C27AE7CC100A19448 /* ctlAboutWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ctlAboutWindow.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; - 5B62A33F27AE7CD900A19448 /* ctlCandidateHorizontal.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ctlCandidateHorizontal.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; - 5B62A34027AE7CD900A19448 /* ctlCandidate.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ctlCandidate.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; - 5B62A34127AE7CD900A19448 /* ctlCandidateVertical.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ctlCandidateVertical.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; - 5B62A34327AE7CD900A19448 /* TooltipController.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = TooltipController.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; - 5B62A34527AE7CD900A19448 /* NotifierController.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = NotifierController.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; + 5B3133BE280B229700A4A505 /* KeyHandler_States.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = KeyHandler_States.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 5B407151281F94E6009C24CB /* Composer.hh */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Composer.hh; sourceTree = ""; }; + 5B407152281F94E6009C24CB /* Composer.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = Composer.mm; sourceTree = ""; }; + 5B407309281672610023DFFF /* lmAssociates.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; lineEnding = 0; path = lmAssociates.swift; sourceTree = ""; usesTabs = 0; }; + 5B40730A281672610023DFFF /* lmReplacements.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; lineEnding = 0; path = lmReplacements.swift; sourceTree = ""; usesTabs = 0; }; + 5B5E535127EF261400C6AA1E /* IME.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = IME.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 5B61B0C9280BEFD4002E3CFA /* KeyHandler_Misc.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = KeyHandler_Misc.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 5B62A32827AE77D100A19448 /* FSEventStreamHelper.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = FSEventStreamHelper.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 5B62A33127AE792F00A19448 /* InputSourceHelper.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = InputSourceHelper.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 5B62A33527AE795800A19448 /* mgrPrefs.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = mgrPrefs.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 5B62A33727AE79CD00A19448 /* NSStringUtils.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = NSStringUtils.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 5B62A33C27AE7CC100A19448 /* ctlAboutWindow.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = ctlAboutWindow.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 5B62A33F27AE7CD900A19448 /* ctlCandidateHorizontal.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = ctlCandidateHorizontal.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 5B62A34027AE7CD900A19448 /* ctlCandidate.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = ctlCandidate.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 5B62A34127AE7CD900A19448 /* ctlCandidateVertical.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = ctlCandidateVertical.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 5B62A34327AE7CD900A19448 /* TooltipController.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = TooltipController.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 5B62A34527AE7CD900A19448 /* NotifierController.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = NotifierController.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; 5B707CE527D9F3A10099EF99 /* SwiftyOpenCC */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = SwiftyOpenCC; path = Packages/SwiftyOpenCC; sourceTree = ""; }; - 5B707CE727D9F4590099EF99 /* OpenCCBridge.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = OpenCCBridge.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; - 5B7111C727DEF9FF00444310 /* UserSymbolLM.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = UserSymbolLM.h; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; + 5B707CE727D9F4590099EF99 /* OpenCCBridge.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = OpenCCBridge.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; 5B73FB5427B2BD6900E9BF49 /* PhraseEditor-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "PhraseEditor-Info.plist"; path = "UserPhraseEditor/PhraseEditor-Info.plist"; sourceTree = SOURCE_ROOT; }; 5B73FB5F27B2BE1300E9BF49 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; - 5B782EC3280C243C007276DE /* KeyHandler_HandleCandidate.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = KeyHandler_HandleCandidate.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; + 5B782EC3280C243C007276DE /* KeyHandler_HandleCandidate.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = KeyHandler_HandleCandidate.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; 5B7BC4AF27AFFBE800F66C24 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/frmPrefWindow.xib; sourceTree = ""; }; 5B7BC4B227AFFC0B00F66C24 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/frmPrefWindow.strings; sourceTree = ""; }; - 5B7F225C2808501000DDD3CB /* KeyHandler_HandleInput.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = KeyHandler_HandleInput.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; - 5B8F43ED27C9BC220069AC27 /* SymbolLM.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = SymbolLM.h; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; - 5BA9FD0A27FEDB6B002DE248 /* suiPrefPaneGeneral.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = suiPrefPaneGeneral.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; - 5BA9FD0B27FEDB6B002DE248 /* suiPrefPaneKeyboard.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = suiPrefPaneKeyboard.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; - 5BA9FD0C27FEDB6B002DE248 /* ctlPrefUI.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ctlPrefUI.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; - 5BA9FD0D27FEDB6B002DE248 /* suiPrefPaneExperience.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = suiPrefPaneExperience.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; - 5BA9FD0E27FEDB6B002DE248 /* suiPrefPaneDictionary.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = suiPrefPaneDictionary.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; - 5BA9FD3127FEF3C8002DE248 /* Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Utilities.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; - 5BA9FD3227FEF3C8002DE248 /* Pane.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Pane.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; - 5BA9FD3327FEF3C8002DE248 /* Localization.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Localization.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; - 5BA9FD3427FEF3C8002DE248 /* PreferencesStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = PreferencesStyle.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; - 5BA9FD3527FEF3C8002DE248 /* PreferencePane.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = PreferencePane.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; - 5BA9FD3627FEF3C8002DE248 /* Preferences.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Preferences.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; - 5BA9FD3727FEF3C8002DE248 /* SegmentedControlStyleViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = SegmentedControlStyleViewController.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; - 5BA9FD3827FEF3C8002DE248 /* ToolbarItemStyleViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ToolbarItemStyleViewController.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; - 5BA9FD3927FEF3C8002DE248 /* Container.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Container.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; - 5BA9FD3A27FEF3C8002DE248 /* PreferencesStyleController.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = PreferencesStyleController.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; - 5BA9FD3B27FEF3C8002DE248 /* PreferencesWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = PreferencesWindowController.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; - 5BA9FD3C27FEF3C8002DE248 /* Section.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Section.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; - 5BA9FD3D27FEF3C8002DE248 /* PreferencesTabViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = PreferencesTabViewController.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; - 5BA9FD8A28006B41002DE248 /* VDKComboBox.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = VDKComboBox.swift; sourceTree = ""; tabWidth = 2; }; - 5BAEFACF28012565001F42C9 /* mgrLangModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = mgrLangModel.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; - 5BB802D927FABA8300CF1C19 /* ctlInputMethod_Menu.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ctlInputMethod_Menu.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; + 5B7F225C2808501000DDD3CB /* KeyHandler_HandleInput.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = KeyHandler_HandleInput.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 5B949BD82816DC5400D87B5D /* LineReader.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; lineEnding = 0; path = LineReader.swift; sourceTree = ""; usesTabs = 0; }; + 5B949BDA2816DDBC00D87B5D /* LMConsolidator.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; lineEnding = 0; path = LMConsolidator.swift; sourceTree = ""; usesTabs = 0; }; + 5BA0DF2E2817857D009E73BB /* lmUserOverride.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; lineEnding = 0; path = lmUserOverride.swift; sourceTree = ""; usesTabs = 0; }; + 5BA0DF2F2817857D009E73BB /* lmCore.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; lineEnding = 0; path = lmCore.swift; sourceTree = ""; usesTabs = 0; }; + 5BA9FD0A27FEDB6B002DE248 /* suiPrefPaneGeneral.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = suiPrefPaneGeneral.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 5BA9FD0B27FEDB6B002DE248 /* suiPrefPaneKeyboard.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = suiPrefPaneKeyboard.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 5BA9FD0C27FEDB6B002DE248 /* ctlPrefUI.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = ctlPrefUI.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 5BA9FD0D27FEDB6B002DE248 /* suiPrefPaneExperience.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = suiPrefPaneExperience.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 5BA9FD0E27FEDB6B002DE248 /* suiPrefPaneDictionary.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = suiPrefPaneDictionary.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 5BA9FD3127FEF3C8002DE248 /* Utilities.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = Utilities.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 5BA9FD3227FEF3C8002DE248 /* Pane.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = Pane.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 5BA9FD3327FEF3C8002DE248 /* Localization.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = Localization.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 5BA9FD3427FEF3C8002DE248 /* PreferencesStyle.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = PreferencesStyle.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 5BA9FD3527FEF3C8002DE248 /* PreferencePane.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = PreferencePane.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 5BA9FD3627FEF3C8002DE248 /* Preferences.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Preferences.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 5BA9FD3727FEF3C8002DE248 /* SegmentedControlStyleViewController.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = SegmentedControlStyleViewController.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 5BA9FD3827FEF3C8002DE248 /* ToolbarItemStyleViewController.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = ToolbarItemStyleViewController.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 5BA9FD3927FEF3C8002DE248 /* Container.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = Container.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 5BA9FD3A27FEF3C8002DE248 /* PreferencesStyleController.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = PreferencesStyleController.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 5BA9FD3B27FEF3C8002DE248 /* PreferencesWindowController.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = PreferencesWindowController.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 5BA9FD3C27FEF3C8002DE248 /* Section.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = Section.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 5BA9FD3D27FEF3C8002DE248 /* PreferencesTabViewController.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = PreferencesTabViewController.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 5BA9FD8A28006B41002DE248 /* VDKComboBox.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = VDKComboBox.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 5BAEFACF28012565001F42C9 /* mgrLangModel.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = mgrLangModel.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 5BB802D927FABA8300CF1C19 /* ctlInputMethod_Menu.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = ctlInputMethod_Menu.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; 5BBBB75D27AED54C0023B93A /* Beep.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = Beep.m4a; sourceTree = ""; }; 5BBBB75E27AED54C0023B93A /* Fart.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = Fart.m4a; sourceTree = ""; }; 5BBBB76627AED5DB0023B93A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/frmNonModalAlertWindow.xib; sourceTree = ""; }; @@ -247,21 +259,23 @@ 5BBBB77127AED70B0023B93A /* MenuIcon-SCVIM.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "MenuIcon-SCVIM.png"; sourceTree = ""; }; 5BBBB77227AED70B0023B93A /* MenuIcon-TCVIM.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "MenuIcon-TCVIM.png"; sourceTree = ""; }; 5BBBB77727AEDB290023B93A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/MainMenu.strings; sourceTree = ""; }; - 5BBBB77927AEDC690023B93A /* clsSFX.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = clsSFX.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; + 5BBBB77927AEDC690023B93A /* clsSFX.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = clsSFX.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; 5BBD627827B6C4D900271480 /* Update-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Update-Info.plist"; sourceTree = ""; }; 5BC0AAC927F58472002D33E9 /* pkgPreInstall.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = pkgPreInstall.sh; sourceTree = ""; }; 5BC0AACA27F58472002D33E9 /* pkgPostInstall.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = pkgPostInstall.sh; sourceTree = ""; }; 5BC2652127E04B7B00700291 /* uninstall.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; lineEnding = 0; path = uninstall.sh; sourceTree = ""; }; + 5BD0113A28180D6100609769 /* LMInstantiator.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; lineEnding = 0; path = LMInstantiator.swift; sourceTree = ""; usesTabs = 0; }; + 5BD0113C2818543900609769 /* KeyHandler_Core.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; lineEnding = 0; path = KeyHandler_Core.swift; sourceTree = ""; usesTabs = 0; }; 5BD05B8027B22F3C004C4F1D /* char-kanji-cns.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "char-kanji-cns.txt"; path = "Data/components/common/char-kanji-cns.txt"; sourceTree = ""; }; 5BD05BB827B2A429004C4F1D /* vChewingPhraseEditor.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = vChewingPhraseEditor.app; sourceTree = BUILT_PRODUCTS_DIR; }; 5BD05BC627B2A42A004C4F1D /* vChewingPhraseEditor.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = vChewingPhraseEditor.entitlements; sourceTree = ""; }; 5BD05C5C27B2BBA9004C4F1D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - 5BD05C6127B2BBEF004C4F1D /* Document.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Document.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; - 5BD05C6227B2BBEF004C4F1D /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = AppDelegate.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; - 5BD05C6327B2BBEF004C4F1D /* Content.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Content.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; - 5BD05C6427B2BBEF004C4F1D /* WindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = WindowController.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; - 5BD05C6527B2BBEF004C4F1D /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ViewController.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; - 5BDC1CF927FDF1310052C2B9 /* apiUpdate.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = apiUpdate.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; + 5BD05C6127B2BBEF004C4F1D /* Document.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = Document.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 5BD05C6227B2BBEF004C4F1D /* AppDelegate.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = AppDelegate.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 5BD05C6327B2BBEF004C4F1D /* Content.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = Content.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 5BD05C6427B2BBEF004C4F1D /* WindowController.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = WindowController.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 5BD05C6527B2BBEF004C4F1D /* ViewController.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = ViewController.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 5BDC1CF927FDF1310052C2B9 /* apiUpdate.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = apiUpdate.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; 5BDCBB4227B4F6C600D0CC59 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/MainMenu.strings"; sourceTree = ""; }; 5BDCBB4327B4F6C600D0CC59 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/frmAboutWindow.strings"; sourceTree = ""; }; 5BDCBB4527B4F6C600D0CC59 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/frmPrefWindow.strings"; sourceTree = ""; }; @@ -271,25 +285,26 @@ 5BDCBB4A27B4F6C700D0CC59 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Localizable.strings"; sourceTree = ""; }; 5BDCBB4B27B4F6C700D0CC59 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/frmAboutWindow.strings"; sourceTree = ""; }; 5BDCBB4D27B4F6C700D0CC59 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/InfoPlist.strings"; sourceTree = ""; }; - 5BE78BD827B37750005EA1BE /* ctlAboutWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ctlAboutWindow.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; + 5BE78BD827B37750005EA1BE /* ctlAboutWindow.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = ctlAboutWindow.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; 5BE78BDB27B37764005EA1BE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/frmAboutWindow.xib; sourceTree = ""; }; 5BE78BDF27B37968005EA1BE /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/frmAboutWindow.strings; sourceTree = ""; }; - 5BF8423027BAA942008E7E4C /* vChewingKanjiConverter.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = vChewingKanjiConverter.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; + 5BE8A8C4281EE65300197741 /* CONTRIBUTING.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CONTRIBUTING.md; sourceTree = ""; }; + 5BF8423027BAA942008E7E4C /* vChewingKanjiConverter.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = vChewingKanjiConverter.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; 5BFDF48C27B51867009523B6 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Main.strings"; sourceTree = ""; }; 6A0D4EA215FC0D2D00ABF4B3 /* vChewing.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = vChewing.app; sourceTree = BUILT_PRODUCTS_DIR; }; 6A0D4EF515FC0DA600ABF4B3 /* IME-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "IME-Info.plist"; sourceTree = ""; }; 6A0D4EF615FC0DA600ABF4B3 /* vChewing-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "vChewing-Prefix.pch"; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; - 6A0D4F1415FC0EB100ABF4B3 /* Bigram.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = Bigram.h; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; - 6A0D4F1515FC0EB100ABF4B3 /* BlockReadingBuilder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = BlockReadingBuilder.h; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; - 6A0D4F1615FC0EB100ABF4B3 /* Gramambular.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = Gramambular.h; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; - 6A0D4F1715FC0EB100ABF4B3 /* Grid.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = Grid.h; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; - 6A0D4F1815FC0EB100ABF4B3 /* KeyValuePair.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = KeyValuePair.h; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; - 6A0D4F1915FC0EB100ABF4B3 /* LanguageModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = LanguageModel.h; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; - 6A0D4F1A15FC0EB100ABF4B3 /* Node.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = Node.h; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; - 6A0D4F1B15FC0EB100ABF4B3 /* NodeAnchor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = NodeAnchor.h; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; - 6A0D4F1C15FC0EB100ABF4B3 /* Span.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = Span.h; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; - 6A0D4F1D15FC0EB100ABF4B3 /* Unigram.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = Unigram.h; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; - 6A0D4F1E15FC0EB100ABF4B3 /* Walker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = Walker.h; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; + 6A0D4F1415FC0EB100ABF4B3 /* 6_Bigram.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = 6_Bigram.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 6A0D4F1515FC0EB100ABF4B3 /* 1_BlockReadingBuilder.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = 1_BlockReadingBuilder.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 6A0D4F1615FC0EB100ABF4B3 /* 0_Megrez.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = 0_Megrez.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 6A0D4F1715FC0EB100ABF4B3 /* 2_Grid.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = 2_Grid.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 6A0D4F1815FC0EB100ABF4B3 /* 7_KeyValuePair.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = 7_KeyValuePair.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 6A0D4F1915FC0EB100ABF4B3 /* 5_LanguageModel.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = 5_LanguageModel.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 6A0D4F1A15FC0EB100ABF4B3 /* 4_Node.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = 4_Node.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 6A0D4F1B15FC0EB100ABF4B3 /* 3_NodeAnchor.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = 3_NodeAnchor.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 6A0D4F1C15FC0EB100ABF4B3 /* 3_Span.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = 3_Span.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 6A0D4F1D15FC0EB100ABF4B3 /* 6_Unigram.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = 6_Unigram.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + 6A0D4F1E15FC0EB100ABF4B3 /* 1_Walker.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = 1_Walker.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; 6A0D4F2015FC0EB100ABF4B3 /* Mandarin.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; lineEnding = 0; path = Mandarin.cpp; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; 6A0D4F2115FC0EB100ABF4B3 /* Mandarin.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = Mandarin.h; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; 6A15B32421A51F2300B92CD3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; @@ -302,39 +317,26 @@ 6ACA41EF15FC1D9000935EF6 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; 6ACA41F215FC1D9000935EF6 /* Installer-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "Installer-Info.plist"; path = "Installer/Installer-Info.plist"; sourceTree = SOURCE_ROOT; }; 6ACA41F315FC1D9000935EF6 /* Installer-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "Installer-Prefix.pch"; path = "Installer/Installer-Prefix.pch"; sourceTree = SOURCE_ROOT; }; - 6ACC3D3C27914AAB00F1B140 /* KeyValueBlobReader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = KeyValueBlobReader.h; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; - 6ACC3D3E27914F2400F1B140 /* KeyValueBlobReader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; lineEnding = 0; path = KeyValueBlobReader.cpp; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; 6ACC3D402793701600F1B140 /* ParselessPhraseDB.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; lineEnding = 0; path = ParselessPhraseDB.cpp; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; 6ACC3D412793701600F1B140 /* ParselessPhraseDB.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ParselessPhraseDB.h; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; 6ACC3D422793701600F1B140 /* ParselessLM.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; lineEnding = 0; path = ParselessLM.cpp; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; 6ACC3D432793701600F1B140 /* ParselessLM.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ParselessLM.h; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; - D41355D6278D7409005E5CBD /* mgrLangModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = mgrLangModel.h; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; - D41355D7278D7409005E5CBD /* mgrLangModel.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = mgrLangModel.mm; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; - D41355D9278E6D17005E5CBD /* LMInstantiator.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = LMInstantiator.mm; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; - D41355DA278E6D17005E5CBD /* LMInstantiator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = LMInstantiator.h; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; - D41355DC278EA3ED005E5CBD /* UserPhrasesLM.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = UserPhrasesLM.mm; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; - D41355DD278EA3ED005E5CBD /* UserPhrasesLM.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = UserPhrasesLM.h; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; - D427A9BF25ED28CC005D43E0 /* vChewing-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = "vChewing-Bridging-Header.h"; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; - D427F76B278CA1BA004A2160 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = AppDelegate.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; - D44FB74B2792189A003C80A6 /* PhraseReplacementMap.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = PhraseReplacementMap.mm; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; - D44FB74C2792189A003C80A6 /* PhraseReplacementMap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = PhraseReplacementMap.h; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; - D456576D279E4F7B00DF6BC9 /* InputHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = InputHandler.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; - D461B791279DAC010070E734 /* InputState.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = InputState.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; - D47B92BF27972AC800458394 /* main.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = main.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; - D47D73AA27A6CAE600255A50 /* AssociatedPhrases.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = AssociatedPhrases.mm; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; - D47D73AB27A6CAE600255A50 /* AssociatedPhrases.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = AssociatedPhrases.h; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; - D47F7DCD278BFB57002F9DD7 /* ctlPrefWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ctlPrefWindow.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; - D47F7DCF278C0897002F9DD7 /* ctlNonModalAlertWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ctlNonModalAlertWindow.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; + D427A9BF25ED28CC005D43E0 /* vChewing-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = "vChewing-Bridging-Header.h"; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; + D427F76B278CA1BA004A2160 /* AppDelegate.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = AppDelegate.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + D456576D279E4F7B00DF6BC9 /* InputHandler.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = InputHandler.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + D461B791279DAC010070E734 /* InputState.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = InputState.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + D47B92BF27972AC800458394 /* main.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = main.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + D47F7DCD278BFB57002F9DD7 /* ctlPrefWindow.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = ctlPrefWindow.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + D47F7DCF278C0897002F9DD7 /* ctlNonModalAlertWindow.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = ctlNonModalAlertWindow.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; D47F7DD1278C1263002F9DD7 /* UserOverrideModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = UserOverrideModel.h; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; D47F7DD2278C1263002F9DD7 /* UserOverrideModel.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; lineEnding = 0; path = UserOverrideModel.cpp; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; - D495583A27A5C6C4006ADE1C /* mgrLangModel_Privates.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = mgrLangModel_Privates.h; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; - D4A13D5927A59D5C003BE359 /* ctlInputMethod.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ctlInputMethod.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; + D4A13D5927A59D5C003BE359 /* ctlInputMethod.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = ctlInputMethod.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; D4E33D8927A838CF006DB1CF /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = ""; }; D4E33D8E27A838F0006DB1CF /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/InfoPlist.strings; sourceTree = ""; }; - D4E569DA27A34CC100AC2CEF /* KeyHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = KeyHandler.h; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; - D4E569DB27A34CC100AC2CEF /* KeyHandler.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = KeyHandler.mm; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; - D4F0BBDE279AF1AF0071253C /* ArchiveUtil.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ArchiveUtil.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; - D4F0BBE0279AF8B30071253C /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = AppDelegate.swift; sourceTree = ""; tabWidth = 2; usesTabs = 1; }; + D4E569DA27A34CC100AC2CEF /* CTools.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = CTools.h; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; + D4E569DB27A34CC100AC2CEF /* CTools.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = CTools.m; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; + D4F0BBDE279AF1AF0071253C /* ArchiveUtil.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = ArchiveUtil.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + D4F0BBE0279AF8B30071253C /* AppDelegate.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = AppDelegate.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; D4F0BBE2279B08900071253C /* Chronosphere.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = Chronosphere.h; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; D4F0BBE3279B08900071253C /* Chronosphere.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = Chronosphere.m; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; /* End PBXFileReference section */ @@ -377,6 +379,7 @@ isa = PBXGroup; children = ( 5BC2652127E04B7B00700291 /* uninstall.sh */, + 5BE8A8C4281EE65300197741 /* CONTRIBUTING.md */, 5B18BA6F27C7BD8B0056EB19 /* LICENSE-CHS.txt */, 5B18BA7427C7BD8C0056EB19 /* LICENSE-CHT.txt */, 5B18BA7327C7BD8C0056EB19 /* LICENSE-JPN.txt */, @@ -386,19 +389,22 @@ name = MiscRootFiles; sourceTree = ""; }; - 5B4D47B627C9186900220DDC /* InstantiatedModels */ = { + 5B407308281672610023DFFF /* SubLMs */ = { isa = PBXGroup; children = ( - 5B62A32B27AE78B000A19448 /* CNSLM.h */, - 5B8F43ED27C9BC220069AC27 /* SymbolLM.h */, - 5B7111C727DEF9FF00444310 /* UserSymbolLM.h */, + 5B407309281672610023DFFF /* lmAssociates.swift */, + 5BA0DF2F2817857D009E73BB /* lmCore.swift */, + 5B00A22F282011980058E5DB /* lmLite.swift */, + 5B40730A281672610023DFFF /* lmReplacements.swift */, + 5BA0DF2E2817857D009E73BB /* lmUserOverride.swift */, ); - path = InstantiatedModels; + path = SubLMs; sourceTree = ""; }; 5B62A30127AE732800A19448 /* 3rdParty */ = { isa = PBXGroup; children = ( + 5B949BD72816DC4400D87B5D /* LineReader */, 5B707CE627D9F43E0099EF99 /* OpenCCBridge */, 5B62A30227AE733500A19448 /* OVMandarin */, 5BA9FCEA27FED652002DE248 /* SindreSorhus */, @@ -412,6 +418,8 @@ children = ( 6A0D4F2015FC0EB100ABF4B3 /* Mandarin.cpp */, 6A0D4F2115FC0EB100ABF4B3 /* Mandarin.h */, + 5B407151281F94E6009C24CB /* Composer.hh */, + 5B407152281F94E6009C24CB /* Composer.mm */, ); path = OVMandarin; sourceTree = ""; @@ -428,16 +436,15 @@ isa = PBXGroup; children = ( 5B11328827B94CFB00E58451 /* AppleKeyboardConverter.swift */, + D4E569DA27A34CC100AC2CEF /* CTools.h */, + D4E569DB27A34CC100AC2CEF /* CTools.m */, D456576D279E4F7B00DF6BC9 /* InputHandler.swift */, D461B791279DAC010070E734 /* InputState.swift */, + 5BD0113C2818543900609769 /* KeyHandler_Core.swift */, 5B782EC3280C243C007276DE /* KeyHandler_HandleCandidate.swift */, 5B7F225C2808501000DDD3CB /* KeyHandler_HandleInput.swift */, 5B61B0C9280BEFD4002E3CFA /* KeyHandler_Misc.swift */, 5B3133BE280B229700A4A505 /* KeyHandler_States.swift */, - D4E569DA27A34CC100AC2CEF /* KeyHandler.h */, - D4E569DB27A34CC100AC2CEF /* KeyHandler.mm */, - 6ACC3D3E27914F2400F1B140 /* KeyValueBlobReader.cpp */, - 6ACC3D3C27914AAB00F1B140 /* KeyValueBlobReader.h */, 5B62A33727AE79CD00A19448 /* NSStringUtils.swift */, 5BF8423027BAA942008E7E4C /* vChewingKanjiConverter.swift */, ); @@ -457,8 +464,6 @@ isa = PBXGroup; children = ( 5B62A32827AE77D100A19448 /* FSEventStreamHelper.swift */, - 5B62A32627AE77BB00A19448 /* LMConsolidator.h */, - 5B62A32727AE77BB00A19448 /* LMConsolidator.mm */, ); path = FileHandlers; sourceTree = ""; @@ -479,7 +484,7 @@ 5B62A32327AE756800A19448 /* LanguageParsers */ = { isa = PBXGroup; children = ( - 6A0D4F1315FC0EB100ABF4B3 /* Gramambular */, + 6A0D4F1315FC0EB100ABF4B3 /* Megrez */, ); path = LanguageParsers; sourceTree = ""; @@ -487,37 +492,26 @@ 5B62A32427AE757300A19448 /* LangModelRelated */ = { isa = PBXGroup; children = ( - 5B62A32527AE758000A19448 /* SubLanguageModels */, - D41355DA278E6D17005E5CBD /* LMInstantiator.h */, - D41355D9278E6D17005E5CBD /* LMInstantiator.mm */, - D495583A27A5C6C4006ADE1C /* mgrLangModel_Privates.h */, - D41355D6278D7409005E5CBD /* mgrLangModel.h */, - D41355D7278D7409005E5CBD /* mgrLangModel.mm */, + 5B62A32527AE758000A19448 /* OldFileReferences */, + 5B407308281672610023DFFF /* SubLMs */, + 5B949BDA2816DDBC00D87B5D /* LMConsolidator.swift */, + 5BD0113A28180D6100609769 /* LMInstantiator.swift */, 5BAEFACF28012565001F42C9 /* mgrLangModel.swift */, ); path = LangModelRelated; sourceTree = ""; }; - 5B62A32527AE758000A19448 /* SubLanguageModels */ = { + 5B62A32527AE758000A19448 /* OldFileReferences */ = { isa = PBXGroup; children = ( - 5B4D47B627C9186900220DDC /* InstantiatedModels */, - D47D73AB27A6CAE600255A50 /* AssociatedPhrases.h */, - D47D73AA27A6CAE600255A50 /* AssociatedPhrases.mm */, - 5B62A32C27AE78B000A19448 /* CoreLM.h */, - 5B62A32D27AE78B000A19448 /* CoreLM.mm */, 6ACC3D422793701600F1B140 /* ParselessLM.cpp */, 6ACC3D432793701600F1B140 /* ParselessLM.h */, 6ACC3D402793701600F1B140 /* ParselessPhraseDB.cpp */, 6ACC3D412793701600F1B140 /* ParselessPhraseDB.h */, - D44FB74C2792189A003C80A6 /* PhraseReplacementMap.h */, - D44FB74B2792189A003C80A6 /* PhraseReplacementMap.mm */, D47F7DD2278C1263002F9DD7 /* UserOverrideModel.cpp */, D47F7DD1278C1263002F9DD7 /* UserOverrideModel.h */, - D41355DD278EA3ED005E5CBD /* UserPhrasesLM.h */, - D41355DC278EA3ED005E5CBD /* UserPhrasesLM.mm */, ); - path = SubLanguageModels; + path = OldFileReferences; sourceTree = ""; }; 5B62A33027AE78E500A19448 /* Resources */ = { @@ -620,6 +614,14 @@ path = OpenCCBridge; sourceTree = ""; }; + 5B949BD72816DC4400D87B5D /* LineReader */ = { + isa = PBXGroup; + children = ( + 5B949BD82816DC5400D87B5D /* LineReader.swift */, + ); + path = LineReader; + sourceTree = ""; + }; 5BA9FCEA27FED652002DE248 /* SindreSorhus */ = { isa = PBXGroup; children = ( @@ -800,22 +802,22 @@ path = Modules; sourceTree = ""; }; - 6A0D4F1315FC0EB100ABF4B3 /* Gramambular */ = { + 6A0D4F1315FC0EB100ABF4B3 /* Megrez */ = { isa = PBXGroup; children = ( - 6A0D4F1415FC0EB100ABF4B3 /* Bigram.h */, - 6A0D4F1515FC0EB100ABF4B3 /* BlockReadingBuilder.h */, - 6A0D4F1615FC0EB100ABF4B3 /* Gramambular.h */, - 6A0D4F1715FC0EB100ABF4B3 /* Grid.h */, - 6A0D4F1815FC0EB100ABF4B3 /* KeyValuePair.h */, - 6A0D4F1915FC0EB100ABF4B3 /* LanguageModel.h */, - 6A0D4F1A15FC0EB100ABF4B3 /* Node.h */, - 6A0D4F1B15FC0EB100ABF4B3 /* NodeAnchor.h */, - 6A0D4F1C15FC0EB100ABF4B3 /* Span.h */, - 6A0D4F1D15FC0EB100ABF4B3 /* Unigram.h */, - 6A0D4F1E15FC0EB100ABF4B3 /* Walker.h */, + 6A0D4F1615FC0EB100ABF4B3 /* 0_Megrez.swift */, + 6A0D4F1515FC0EB100ABF4B3 /* 1_BlockReadingBuilder.swift */, + 6A0D4F1E15FC0EB100ABF4B3 /* 1_Walker.swift */, + 6A0D4F1715FC0EB100ABF4B3 /* 2_Grid.swift */, + 6A0D4F1B15FC0EB100ABF4B3 /* 3_NodeAnchor.swift */, + 6A0D4F1C15FC0EB100ABF4B3 /* 3_Span.swift */, + 6A0D4F1A15FC0EB100ABF4B3 /* 4_Node.swift */, + 6A0D4F1915FC0EB100ABF4B3 /* 5_LanguageModel.swift */, + 6A0D4F1415FC0EB100ABF4B3 /* 6_Bigram.swift */, + 6A0D4F1D15FC0EB100ABF4B3 /* 6_Unigram.swift */, + 6A0D4F1815FC0EB100ABF4B3 /* 7_KeyValuePair.swift */, ); - path = Gramambular; + path = Megrez; sourceTree = ""; }; 6ACA41E715FC1D9000935EF6 /* Installer */ = { @@ -1069,66 +1071,76 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 5B38F59D281E2E49007D5F5D /* 4_Node.swift in Sources */, + 5B38F5A3281E2E49007D5F5D /* 3_Span.swift in Sources */, + 5B40730C281672610023DFFF /* lmAssociates.swift in Sources */, 5B707CE827D9F4590099EF99 /* OpenCCBridge.swift in Sources */, D427F76C278CA2B0004A2160 /* AppDelegate.swift in Sources */, 5BA9FD4527FEF3C9002DE248 /* ToolbarItemStyleViewController.swift in Sources */, + 5BA0DF322817857D009E73BB /* lmCore.swift in Sources */, 5BA9FD4127FEF3C8002DE248 /* PreferencesStyle.swift in Sources */, 5B7F225D2808501000DDD3CB /* KeyHandler_HandleInput.swift in Sources */, 5BA9FD1227FEDB6B002DE248 /* suiPrefPaneExperience.swift in Sources */, - 6ACC3D442793701600F1B140 /* ParselessPhraseDB.cpp in Sources */, D461B792279DAC010070E734 /* InputState.swift in Sources */, 5B62A33D27AE7CC100A19448 /* ctlAboutWindow.swift in Sources */, D47B92C027972AD100458394 /* main.swift in Sources */, - D44FB74D2792189A003C80A6 /* PhraseReplacementMap.mm in Sources */, D4A13D5A27A59F0B003BE359 /* ctlInputMethod.swift in Sources */, 5BA9FD4827FEF3C9002DE248 /* PreferencesWindowController.swift in Sources */, - D4E569DC27A34D0E00AC2CEF /* KeyHandler.mm in Sources */, + 5BD0113B28180D6100609769 /* LMInstantiator.swift in Sources */, + D4E569DC27A34D0E00AC2CEF /* CTools.m in Sources */, 5BA9FD4627FEF3C9002DE248 /* Container.swift in Sources */, D47F7DD0278C0897002F9DD7 /* ctlNonModalAlertWindow.swift in Sources */, - 5B62A32F27AE78B000A19448 /* CoreLM.mm in Sources */, - 5BE78BE027B38804005EA1BE /* LMConsolidator.mm in Sources */, + 5B38F5A2281E2E49007D5F5D /* 0_Megrez.swift in Sources */, + 5B949BD92816DC5400D87B5D /* LineReader.swift in Sources */, D456576E279E4F7B00DF6BC9 /* InputHandler.swift in Sources */, 5BA9FD1027FEDB6B002DE248 /* suiPrefPaneKeyboard.swift in Sources */, 5B3133BF280B229700A4A505 /* KeyHandler_States.swift in Sources */, 5BA9FD4327FEF3C8002DE248 /* Preferences.swift in Sources */, 5BA9FD4427FEF3C8002DE248 /* SegmentedControlStyleViewController.swift in Sources */, D47F7DCE278BFB57002F9DD7 /* ctlPrefWindow.swift in Sources */, + 5BD0113D2818543900609769 /* KeyHandler_Core.swift in Sources */, 5BA9FD4227FEF3C8002DE248 /* PreferencePane.swift in Sources */, + 5BA0DF312817857D009E73BB /* lmUserOverride.swift in Sources */, 5BA9FD8B28006B41002DE248 /* VDKComboBox.swift in Sources */, - D47D73AC27A6CAE600255A50 /* AssociatedPhrases.mm in Sources */, 5BA9FD4A27FEF3C9002DE248 /* PreferencesTabViewController.swift in Sources */, 5B62A34A27AE7CD900A19448 /* NotifierController.swift in Sources */, 5B11328927B94CFB00E58451 /* AppleKeyboardConverter.swift in Sources */, - D41355DB278E6D17005E5CBD /* LMInstantiator.mm in Sources */, 5B62A32927AE77D100A19448 /* FSEventStreamHelper.swift in Sources */, - D47F7DD3278C1263002F9DD7 /* UserOverrideModel.cpp in Sources */, + 5B38F59B281E2E49007D5F5D /* 7_KeyValuePair.swift in Sources */, 5B62A33627AE795800A19448 /* mgrPrefs.swift in Sources */, + 5B38F5A4281E2E49007D5F5D /* 5_LanguageModel.swift in Sources */, 5BAEFAD028012565001F42C9 /* mgrLangModel.swift in Sources */, 5B782EC4280C243C007276DE /* KeyHandler_HandleCandidate.swift in Sources */, 5B62A33827AE79CD00A19448 /* NSStringUtils.swift in Sources */, 5BA9FD0F27FEDB6B002DE248 /* suiPrefPaneGeneral.swift in Sources */, 5BA9FD4927FEF3C9002DE248 /* Section.swift in Sources */, + 5B407153281F94E6009C24CB /* Composer.mm in Sources */, 5BA9FD3E27FEF3C8002DE248 /* Utilities.swift in Sources */, 5BA9FD1127FEDB6B002DE248 /* ctlPrefUI.swift in Sources */, + 5B38F59C281E2E49007D5F5D /* 2_Grid.swift in Sources */, + 5B40730D281672610023DFFF /* lmReplacements.swift in Sources */, + 5B38F59E281E2E49007D5F5D /* 6_Bigram.swift in Sources */, 5B62A33227AE792F00A19448 /* InputSourceHelper.swift in Sources */, 5B5E535227EF261400C6AA1E /* IME.swift in Sources */, 5B62A34927AE7CD900A19448 /* TooltipController.swift in Sources */, 6A0D4F4515FC0EB100ABF4B3 /* Mandarin.cpp in Sources */, 5B61B0CA280BEFD4002E3CFA /* KeyHandler_Misc.swift in Sources */, + 5B38F59A281E2E49007D5F5D /* 6_Unigram.swift in Sources */, + 5B38F5A0281E2E49007D5F5D /* 1_Walker.swift in Sources */, 5B62A34827AE7CD900A19448 /* ctlCandidateVertical.swift in Sources */, 5BA9FD4027FEF3C8002DE248 /* Localization.swift in Sources */, 5BA9FD1327FEDB6B002DE248 /* suiPrefPaneDictionary.swift in Sources */, - 6ACC3D452793701600F1B140 /* ParselessLM.cpp in Sources */, + 5B00A230282011980058E5DB /* lmLite.swift in Sources */, 5BBBB77A27AEDC690023B93A /* clsSFX.swift in Sources */, 5BA9FD4727FEF3C9002DE248 /* PreferencesStyleController.swift in Sources */, 5BF8423127BAA942008E7E4C /* vChewingKanjiConverter.swift in Sources */, + 5B949BDB2816DDBC00D87B5D /* LMConsolidator.swift in Sources */, + 5B38F59F281E2E49007D5F5D /* 3_NodeAnchor.swift in Sources */, 5B62A34627AE7CD900A19448 /* ctlCandidateHorizontal.swift in Sources */, 5B62A34727AE7CD900A19448 /* ctlCandidate.swift in Sources */, 5BA9FD3F27FEF3C8002DE248 /* Pane.swift in Sources */, 5BB802DA27FABA8300CF1C19 /* ctlInputMethod_Menu.swift in Sources */, - D41355DE278EA3ED005E5CBD /* UserPhrasesLM.mm in Sources */, - 6ACC3D3F27914F2400F1B140 /* KeyValueBlobReader.cpp in Sources */, - D41355D8278D74B5005E5CBD /* mgrLangModel.mm in Sources */, + 5B38F5A1281E2E49007D5F5D /* 1_BlockReadingBuilder.swift in Sources */, 5BDC1CFA27FDF1310052C2B9 /* apiUpdate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1490,6 +1502,7 @@ "$(OTHER_CFLAGS)", "-fcxx-modules", ); + SWIFT_COMPILATION_MODE = wholemodule; }; name = Release; };