Proj // Swift-format tweaks.
This commit is contained in:
parent
6d7f260991
commit
c8f83a28aa
|
@ -31,10 +31,12 @@ extension String {
|
||||||
// Ref: https://stackoverflow.com/a/40993403/4162914 && https://stackoverflow.com/a/71291137/4162914
|
// Ref: https://stackoverflow.com/a/40993403/4162914 && https://stackoverflow.com/a/71291137/4162914
|
||||||
do {
|
do {
|
||||||
let regex = try NSRegularExpression(
|
let regex = try NSRegularExpression(
|
||||||
pattern: pattern, options: [.caseInsensitive, .anchorsMatchLines])
|
pattern: pattern, options: [.caseInsensitive, .anchorsMatchLines]
|
||||||
let range = NSRange(self.startIndex..., in: self)
|
)
|
||||||
|
let range = NSRange(startIndex..., in: self)
|
||||||
self = regex.stringByReplacingMatches(
|
self = regex.stringByReplacingMatches(
|
||||||
in: self, options: [], range: range, withTemplate: replaceWith)
|
in: self, options: [], range: range, withTemplate: replaceWith
|
||||||
|
)
|
||||||
} catch { return }
|
} catch { return }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,9 +61,11 @@ if CommandLine.arguments.count == 3 {
|
||||||
}
|
}
|
||||||
|
|
||||||
strXcodeProjContent.regReplace(
|
strXcodeProjContent.regReplace(
|
||||||
pattern: #"CURRENT_PROJECT_VERSION = .*$"#, replaceWith: "CURRENT_PROJECT_VERSION = " + verBuild + ";")
|
pattern: #"CURRENT_PROJECT_VERSION = .*$"#, replaceWith: "CURRENT_PROJECT_VERSION = " + verBuild + ";"
|
||||||
|
)
|
||||||
strXcodeProjContent.regReplace(
|
strXcodeProjContent.regReplace(
|
||||||
pattern: #"MARKETING_VERSION = .*$"#, replaceWith: "MARKETING_VERSION = " + verMarket + ";")
|
pattern: #"MARKETING_VERSION = .*$"#, replaceWith: "MARKETING_VERSION = " + verMarket + ";"
|
||||||
|
)
|
||||||
do {
|
do {
|
||||||
try strXcodeProjContent.write(to: URL(fileURLWithPath: dirXcodeProjectFile), atomically: false, encoding: .utf8)
|
try strXcodeProjContent.write(to: URL(fileURLWithPath: dirXcodeProjectFile), atomically: false, encoding: .utf8)
|
||||||
} catch {
|
} catch {
|
||||||
|
@ -81,5 +85,4 @@ if CommandLine.arguments.count == 3 {
|
||||||
theDictionary?.setValue(verMarket, forKeyPath: "CFBundleShortVersionString")
|
theDictionary?.setValue(verMarket, forKeyPath: "CFBundleShortVersionString")
|
||||||
theDictionary?.write(toFile: dirUpdateInfoPlist, atomically: true)
|
theDictionary?.write(toFile: dirUpdateInfoPlist, atomically: true)
|
||||||
NSLog(" - 更新用通知 plist 版本資訊更新完成:\(verMarket) \(verBuild)。")
|
NSLog(" - 更新用通知 plist 版本資訊更新完成:\(verMarket) \(verBuild)。")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,20 +27,24 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
// MARK: - 前導工作
|
// MARK: - 前導工作
|
||||||
|
|
||||||
extension String {
|
extension String {
|
||||||
fileprivate mutating func regReplace(pattern: String, replaceWith: String = "") {
|
fileprivate mutating func regReplace(pattern: String, replaceWith: String = "") {
|
||||||
// Ref: https://stackoverflow.com/a/40993403/4162914 && https://stackoverflow.com/a/71291137/4162914
|
// Ref: https://stackoverflow.com/a/40993403/4162914 && https://stackoverflow.com/a/71291137/4162914
|
||||||
do {
|
do {
|
||||||
let regex = try NSRegularExpression(
|
let regex = try NSRegularExpression(
|
||||||
pattern: pattern, options: [.caseInsensitive, .anchorsMatchLines])
|
pattern: pattern, options: [.caseInsensitive, .anchorsMatchLines]
|
||||||
let range = NSRange(self.startIndex..., in: self)
|
)
|
||||||
|
let range = NSRange(startIndex..., in: self)
|
||||||
self = regex.stringByReplacingMatches(
|
self = regex.stringByReplacingMatches(
|
||||||
in: self, options: [], range: range, withTemplate: replaceWith)
|
in: self, options: [], range: range, withTemplate: replaceWith
|
||||||
|
)
|
||||||
} catch { return }
|
} catch { return }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 引入小數點位數控制函數
|
// MARK: - 引入小數點位數控制函數
|
||||||
|
|
||||||
// Ref: https://stackoverflow.com/a/32581409/4162914
|
// Ref: https://stackoverflow.com/a/32581409/4162914
|
||||||
extension Float {
|
extension Float {
|
||||||
fileprivate func rounded(toPlaces places: Int) -> Float {
|
fileprivate func rounded(toPlaces places: Int) -> Float {
|
||||||
|
@ -50,6 +54,7 @@ extension Float {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 引入幂乘函數
|
// MARK: - 引入幂乘函數
|
||||||
|
|
||||||
// Ref: https://stackoverflow.com/a/41581695/4162914
|
// Ref: https://stackoverflow.com/a/41581695/4162914
|
||||||
precedencegroup ExponentiationPrecedence {
|
precedencegroup ExponentiationPrecedence {
|
||||||
associativity: right
|
associativity: right
|
||||||
|
@ -59,11 +64,11 @@ precedencegroup ExponentiationPrecedence {
|
||||||
infix operator **: ExponentiationPrecedence
|
infix operator **: ExponentiationPrecedence
|
||||||
|
|
||||||
func ** (_ base: Double, _ exp: Double) -> Double {
|
func ** (_ base: Double, _ exp: Double) -> Double {
|
||||||
return pow(base, exp)
|
pow(base, exp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ** (_ base: Float, _ exp: Float) -> Float {
|
func ** (_ base: Float, _ exp: Float) -> Float {
|
||||||
return pow(base, exp)
|
pow(base, exp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 定義檔案結構
|
// MARK: - 定義檔案結構
|
||||||
|
@ -101,7 +106,7 @@ private let urlOutputCHT: String = "./data-cht.txt"
|
||||||
|
|
||||||
func rawDictForPhrases(isCHS: Bool) -> [Entry] {
|
func rawDictForPhrases(isCHS: Bool) -> [Entry] {
|
||||||
var arrEntryRAW: [Entry] = []
|
var arrEntryRAW: [Entry] = []
|
||||||
var strRAW: String = ""
|
var strRAW = ""
|
||||||
let urlCustom: String = isCHS ? urlCHSforCustom : urlCHTforCustom
|
let urlCustom: String = isCHS ? urlCHSforCustom : urlCHTforCustom
|
||||||
let urlMCBP: String = isCHS ? urlCHSforMCBP : urlCHTforMCBP
|
let urlMCBP: String = isCHS ? urlCHSforMCBP : urlCHTforMCBP
|
||||||
let urlMOE: String = isCHS ? urlCHSforMOE : urlCHTforMOE
|
let urlMOE: String = isCHS ? urlCHSforMOE : urlCHTforMOE
|
||||||
|
@ -136,7 +141,7 @@ func rawDictForPhrases(isCHS: Bool) -> [Entry] {
|
||||||
for lineData in arrData {
|
for lineData in arrData {
|
||||||
// 第三欄開始是注音
|
// 第三欄開始是注音
|
||||||
let arrLineData = lineData.components(separatedBy: " ")
|
let arrLineData = lineData.components(separatedBy: " ")
|
||||||
var varLineDataProcessed: String = ""
|
var varLineDataProcessed = ""
|
||||||
var count = 0
|
var count = 0
|
||||||
for currentCell in arrLineData {
|
for currentCell in arrLineData {
|
||||||
count += 1
|
count += 1
|
||||||
|
@ -165,9 +170,10 @@ func rawDictForPhrases(isCHS: Bool) -> [Entry] {
|
||||||
}
|
}
|
||||||
if phrase != "" { // 廢掉空數據;之後無須再這樣處理。
|
if phrase != "" { // 廢掉空數據;之後無須再這樣處理。
|
||||||
arrEntryRAW += [
|
arrEntryRAW += [
|
||||||
Entry.init(
|
Entry(
|
||||||
valPhone: phone, valPhrase: phrase, valWeight: 0.0,
|
valPhone: phone, valPhrase: phrase, valWeight: 0.0,
|
||||||
valCount: occurrence)
|
valCount: occurrence
|
||||||
|
)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -179,7 +185,7 @@ func rawDictForPhrases(isCHS: Bool) -> [Entry] {
|
||||||
|
|
||||||
func rawDictForKanjis(isCHS: Bool) -> [Entry] {
|
func rawDictForKanjis(isCHS: Bool) -> [Entry] {
|
||||||
var arrEntryRAW: [Entry] = []
|
var arrEntryRAW: [Entry] = []
|
||||||
var strRAW: String = ""
|
var strRAW = ""
|
||||||
let i18n: String = isCHS ? "簡體中文" : "繁體中文"
|
let i18n: String = isCHS ? "簡體中文" : "繁體中文"
|
||||||
// 讀取內容
|
// 讀取內容
|
||||||
do {
|
do {
|
||||||
|
@ -201,7 +207,7 @@ func rawDictForKanjis(isCHS: Bool) -> [Entry] {
|
||||||
// 正式整理格式,現在就開始去重複:
|
// 正式整理格式,現在就開始去重複:
|
||||||
let arrData = Array(
|
let arrData = Array(
|
||||||
NSOrderedSet(array: strRAW.components(separatedBy: "\n")).array as! [String])
|
NSOrderedSet(array: strRAW.components(separatedBy: "\n")).array as! [String])
|
||||||
var varLineData: String = ""
|
var varLineData = ""
|
||||||
for lineData in arrData {
|
for lineData in arrData {
|
||||||
// 簡體中文的話,提取 1,2,4;繁體中文的話,提取 1,3,4。
|
// 簡體中文的話,提取 1,2,4;繁體中文的話,提取 1,3,4。
|
||||||
let varLineDataPre = lineData.components(separatedBy: " ").prefix(isCHS ? 2 : 1)
|
let varLineDataPre = lineData.components(separatedBy: " ").prefix(isCHS ? 2 : 1)
|
||||||
|
@ -212,7 +218,7 @@ func rawDictForKanjis(isCHS: Bool) -> [Entry] {
|
||||||
separator: "\t")
|
separator: "\t")
|
||||||
varLineData = varLineDataPre + "\t" + varLineDataPost
|
varLineData = varLineDataPre + "\t" + varLineDataPost
|
||||||
let arrLineData = varLineData.components(separatedBy: " ")
|
let arrLineData = varLineData.components(separatedBy: " ")
|
||||||
var varLineDataProcessed: String = ""
|
var varLineDataProcessed = ""
|
||||||
var count = 0
|
var count = 0
|
||||||
for currentCell in arrLineData {
|
for currentCell in arrLineData {
|
||||||
count += 1
|
count += 1
|
||||||
|
@ -241,9 +247,10 @@ func rawDictForKanjis(isCHS: Bool) -> [Entry] {
|
||||||
}
|
}
|
||||||
if phrase != "" { // 廢掉空數據;之後無須再這樣處理。
|
if phrase != "" { // 廢掉空數據;之後無須再這樣處理。
|
||||||
arrEntryRAW += [
|
arrEntryRAW += [
|
||||||
Entry.init(
|
Entry(
|
||||||
valPhone: phone, valPhrase: phrase, valWeight: 0.0,
|
valPhone: phone, valPhrase: phrase, valWeight: 0.0,
|
||||||
valCount: occurrence)
|
valCount: occurrence
|
||||||
|
)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -255,7 +262,7 @@ func rawDictForKanjis(isCHS: Bool) -> [Entry] {
|
||||||
|
|
||||||
func rawDictForNonKanjis(isCHS: Bool) -> [Entry] {
|
func rawDictForNonKanjis(isCHS: Bool) -> [Entry] {
|
||||||
var arrEntryRAW: [Entry] = []
|
var arrEntryRAW: [Entry] = []
|
||||||
var strRAW: String = ""
|
var strRAW = ""
|
||||||
let i18n: String = isCHS ? "簡體中文" : "繁體中文"
|
let i18n: String = isCHS ? "簡體中文" : "繁體中文"
|
||||||
// 讀取內容
|
// 讀取內容
|
||||||
do {
|
do {
|
||||||
|
@ -279,14 +286,14 @@ func rawDictForNonKanjis(isCHS: Bool) -> [Entry] {
|
||||||
// 正式整理格式,現在就開始去重複:
|
// 正式整理格式,現在就開始去重複:
|
||||||
let arrData = Array(
|
let arrData = Array(
|
||||||
NSOrderedSet(array: strRAW.components(separatedBy: "\n")).array as! [String])
|
NSOrderedSet(array: strRAW.components(separatedBy: "\n")).array as! [String])
|
||||||
var varLineData: String = ""
|
var varLineData = ""
|
||||||
for lineData in arrData {
|
for lineData in arrData {
|
||||||
varLineData = lineData
|
varLineData = lineData
|
||||||
// 先完成某兩步需要分行處理才能完成的格式整理。
|
// 先完成某兩步需要分行處理才能完成的格式整理。
|
||||||
varLineData = varLineData.components(separatedBy: " ").prefix(3).joined(
|
varLineData = varLineData.components(separatedBy: " ").prefix(3).joined(
|
||||||
separator: "\t") // 提取前三欄的內容。
|
separator: "\t") // 提取前三欄的內容。
|
||||||
let arrLineData = varLineData.components(separatedBy: " ")
|
let arrLineData = varLineData.components(separatedBy: " ")
|
||||||
var varLineDataProcessed: String = ""
|
var varLineDataProcessed = ""
|
||||||
var count = 0
|
var count = 0
|
||||||
for currentCell in arrLineData {
|
for currentCell in arrLineData {
|
||||||
count += 1
|
count += 1
|
||||||
|
@ -315,9 +322,10 @@ func rawDictForNonKanjis(isCHS: Bool) -> [Entry] {
|
||||||
}
|
}
|
||||||
if phrase != "" { // 廢掉空數據;之後無須再這樣處理。
|
if phrase != "" { // 廢掉空數據;之後無須再這樣處理。
|
||||||
arrEntryRAW += [
|
arrEntryRAW += [
|
||||||
Entry.init(
|
Entry(
|
||||||
valPhone: phone, valPhrase: phrase, valWeight: 0.0,
|
valPhone: phone, valPhrase: phrase, valWeight: 0.0,
|
||||||
valCount: occurrence)
|
valCount: occurrence
|
||||||
|
)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -356,16 +364,17 @@ func weightAndSort(_ arrStructUncalculated: [Entry], isCHS: Bool) -> [Entry] {
|
||||||
}
|
}
|
||||||
let weightRounded: Float = weight.rounded(toPlaces: 3) // 為了節省生成的檔案體積,僅保留小數點後三位。
|
let weightRounded: Float = weight.rounded(toPlaces: 3) // 為了節省生成的檔案體積,僅保留小數點後三位。
|
||||||
arrStructCalculated += [
|
arrStructCalculated += [
|
||||||
Entry.init(
|
Entry(
|
||||||
valPhone: entry.valPhone, valPhrase: entry.valPhrase, valWeight: weightRounded,
|
valPhone: entry.valPhone, valPhrase: entry.valPhrase, valWeight: weightRounded,
|
||||||
valCount: entry.valCount)
|
valCount: entry.valCount
|
||||||
|
)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
NSLog(" - \(i18n): 成功計算權重。")
|
NSLog(" - \(i18n): 成功計算權重。")
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// 接下來是排序,先按照注音遞減排序一遍、再按照權重遞減排序一遍。
|
// 接下來是排序,先按照注音遞減排序一遍、再按照權重遞減排序一遍。
|
||||||
let arrStructSorted: [Entry] = arrStructCalculated.sorted(by: { (lhs, rhs) -> Bool in
|
let arrStructSorted: [Entry] = arrStructCalculated.sorted(by: { lhs, rhs -> Bool in
|
||||||
return (lhs.valPhone, rhs.valCount) < (rhs.valPhone, lhs.valCount)
|
(lhs.valPhone, rhs.valCount) < (rhs.valPhone, lhs.valCount)
|
||||||
})
|
})
|
||||||
NSLog(" - \(i18n): 排序整理完畢,準備編譯要寫入的檔案內容。")
|
NSLog(" - \(i18n): 排序整理完畢,準備編譯要寫入的檔案內容。")
|
||||||
return arrStructSorted
|
return arrStructSorted
|
||||||
|
@ -406,6 +415,7 @@ func fileOutput(isCHS: Bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 主执行绪
|
// MARK: - 主执行绪
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
NSLog("// 準備編譯繁體中文核心語料檔案。")
|
NSLog("// 準備編譯繁體中文核心語料檔案。")
|
||||||
fileOutput(isCHS: false)
|
fileOutput(isCHS: false)
|
||||||
|
|
|
@ -31,7 +31,8 @@ private let kTargetType = "app"
|
||||||
private let kTargetBundle = "vChewing.app"
|
private let kTargetBundle = "vChewing.app"
|
||||||
|
|
||||||
private let urlDestinationPartial = FileManager.default.urls(
|
private let urlDestinationPartial = FileManager.default.urls(
|
||||||
for: .inputMethodsDirectory, in: .userDomainMask)[0]
|
for: .inputMethodsDirectory, in: .userDomainMask
|
||||||
|
)[0]
|
||||||
private let urlTargetPartial = urlDestinationPartial.appendingPathComponent(kTargetBundle)
|
private let urlTargetPartial = urlDestinationPartial.appendingPathComponent(kTargetBundle)
|
||||||
private let urlTargetFullBinPartial = urlTargetPartial.appendingPathComponent("Contents/MacOS/")
|
private let urlTargetFullBinPartial = urlTargetPartial.appendingPathComponent("Contents/MacOS/")
|
||||||
.appendingPathComponent(kTargetBin)
|
.appendingPathComponent(kTargetBin)
|
||||||
|
@ -46,12 +47,12 @@ private let kTranslocationRemovalDeadline: TimeInterval = 60.0
|
||||||
@NSApplicationMain
|
@NSApplicationMain
|
||||||
@objc(AppDelegate)
|
@objc(AppDelegate)
|
||||||
class AppDelegate: NSWindowController, NSApplicationDelegate {
|
class AppDelegate: NSWindowController, NSApplicationDelegate {
|
||||||
@IBOutlet weak private var installButton: NSButton!
|
@IBOutlet private var installButton: NSButton!
|
||||||
@IBOutlet weak private var cancelButton: NSButton!
|
@IBOutlet private var cancelButton: NSButton!
|
||||||
@IBOutlet weak private var progressSheet: NSWindow!
|
@IBOutlet private var progressSheet: NSWindow!
|
||||||
@IBOutlet weak private var progressIndicator: NSProgressIndicator!
|
@IBOutlet private var progressIndicator: NSProgressIndicator!
|
||||||
@IBOutlet weak private var appVersionLabel: NSTextField!
|
@IBOutlet private var appVersionLabel: NSTextField!
|
||||||
@IBOutlet weak private var appCopyrightLabel: NSTextField!
|
@IBOutlet private var appCopyrightLabel: NSTextField!
|
||||||
@IBOutlet private var appEULAContent: NSTextView!
|
@IBOutlet private var appEULAContent: NSTextView!
|
||||||
|
|
||||||
private var archiveUtil: ArchiveUtil?
|
private var archiveUtil: ArchiveUtil?
|
||||||
|
@ -69,7 +70,7 @@ class AppDelegate: NSWindowController, NSApplicationDelegate {
|
||||||
alert.runModal()
|
alert.runModal()
|
||||||
}
|
}
|
||||||
|
|
||||||
func applicationDidFinishLaunching(_ notification: Notification) {
|
func applicationDidFinishLaunching(_: Notification) {
|
||||||
guard
|
guard
|
||||||
let installingVersion = Bundle.main.infoDictionary?[kCFBundleVersionKey as String]
|
let installingVersion = Bundle.main.infoDictionary?[kCFBundleVersionKey as String]
|
||||||
as? String,
|
as? String,
|
||||||
|
@ -96,11 +97,13 @@ class AppDelegate: NSWindowController, NSApplicationDelegate {
|
||||||
appEULAContent.string = eulaContent
|
appEULAContent.string = eulaContent
|
||||||
}
|
}
|
||||||
appVersionLabel.stringValue = String(
|
appVersionLabel.stringValue = String(
|
||||||
format: "%@ Build %@", versionString, installingVersion)
|
format: "%@ Build %@", versionString, installingVersion
|
||||||
|
)
|
||||||
|
|
||||||
window?.title = String(
|
window?.title = String(
|
||||||
format: NSLocalizedString("%@ (for version %@, r%@)", comment: ""), window?.title ?? "",
|
format: NSLocalizedString("%@ (for version %@, r%@)", comment: ""), window?.title ?? "",
|
||||||
versionString, installingVersion)
|
versionString, installingVersion
|
||||||
|
)
|
||||||
window?.standardWindowButton(.closeButton)?.isHidden = true
|
window?.standardWindowButton(.closeButton)?.isHidden = true
|
||||||
window?.standardWindowButton(.miniaturizeButton)?.isHidden = true
|
window?.standardWindowButton(.miniaturizeButton)?.isHidden = true
|
||||||
window?.standardWindowButton(.zoomButton)?.isHidden = true
|
window?.standardWindowButton(.zoomButton)?.isHidden = true
|
||||||
|
@ -130,7 +133,7 @@ class AppDelegate: NSWindowController, NSApplicationDelegate {
|
||||||
NSApp.activate(ignoringOtherApps: true)
|
NSApp.activate(ignoringOtherApps: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func agreeAndInstallAction(_ sender: AnyObject) {
|
@IBAction func agreeAndInstallAction(_: AnyObject) {
|
||||||
cancelButton.isEnabled = false
|
cancelButton.isEnabled = false
|
||||||
installButton.isEnabled = false
|
installButton.isEnabled = false
|
||||||
removeThenInstallInputMethod()
|
removeThenInstallInputMethod()
|
||||||
|
@ -154,7 +157,8 @@ class AppDelegate: NSWindowController, NSApplicationDelegate {
|
||||||
== false
|
== false
|
||||||
{
|
{
|
||||||
installInputMethod(
|
installInputMethod(
|
||||||
previousExists: false, previousVersionNotFullyDeactivatedWarning: false)
|
previousExists: false, previousVersionNotFullyDeactivatedWarning: false
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,11 +198,13 @@ class AppDelegate: NSWindowController, NSApplicationDelegate {
|
||||||
if returnCode == .continue {
|
if returnCode == .continue {
|
||||||
self.installInputMethod(
|
self.installInputMethod(
|
||||||
previousExists: true,
|
previousExists: true,
|
||||||
previousVersionNotFullyDeactivatedWarning: false)
|
previousVersionNotFullyDeactivatedWarning: false
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
self.installInputMethod(
|
self.installInputMethod(
|
||||||
previousExists: true,
|
previousExists: true,
|
||||||
previousVersionNotFullyDeactivatedWarning: true)
|
previousVersionNotFullyDeactivatedWarning: true
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -206,15 +212,17 @@ class AppDelegate: NSWindowController, NSApplicationDelegate {
|
||||||
translocationRemovalStartTime = Date()
|
translocationRemovalStartTime = Date()
|
||||||
Timer.scheduledTimer(
|
Timer.scheduledTimer(
|
||||||
timeInterval: kTranslocationRemovalTickInterval, target: self,
|
timeInterval: kTranslocationRemovalTickInterval, target: self,
|
||||||
selector: #selector(timerTick(_:)), userInfo: nil, repeats: true)
|
selector: #selector(timerTick(_:)), userInfo: nil, repeats: true
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
installInputMethod(
|
installInputMethod(
|
||||||
previousExists: false, previousVersionNotFullyDeactivatedWarning: false)
|
previousExists: false, previousVersionNotFullyDeactivatedWarning: false
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func installInputMethod(
|
func installInputMethod(
|
||||||
previousExists: Bool, previousVersionNotFullyDeactivatedWarning warning: Bool
|
previousExists _: Bool, previousVersionNotFullyDeactivatedWarning warning: Bool
|
||||||
) {
|
) {
|
||||||
guard
|
guard
|
||||||
let targetBundle = archiveUtil?.unzipNotarizedArchive()
|
let targetBundle = archiveUtil?.unzipNotarizedArchive()
|
||||||
|
@ -234,7 +242,8 @@ class AppDelegate: NSWindowController, NSApplicationDelegate {
|
||||||
runAlertPanel(
|
runAlertPanel(
|
||||||
title: NSLocalizedString("Install Failed", comment: ""),
|
title: NSLocalizedString("Install Failed", comment: ""),
|
||||||
message: NSLocalizedString("Cannot copy the file to the destination.", comment: ""),
|
message: NSLocalizedString("Cannot copy the file to the destination.", comment: ""),
|
||||||
buttonTitle: NSLocalizedString("Cancel", comment: ""))
|
buttonTitle: NSLocalizedString("Cancel", comment: "")
|
||||||
|
)
|
||||||
endAppWithDelay()
|
endAppWithDelay()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -254,11 +263,14 @@ class AppDelegate: NSWindowController, NSApplicationDelegate {
|
||||||
if !status {
|
if !status {
|
||||||
let message = String(
|
let message = String(
|
||||||
format: NSLocalizedString(
|
format: NSLocalizedString(
|
||||||
"Cannot find input source %@ after registration.", comment: ""),
|
"Cannot find input source %@ after registration.", comment: ""
|
||||||
imeIdentifier)
|
),
|
||||||
|
imeIdentifier
|
||||||
|
)
|
||||||
runAlertPanel(
|
runAlertPanel(
|
||||||
title: NSLocalizedString("Fatal Error", comment: ""), message: message,
|
title: NSLocalizedString("Fatal Error", comment: ""), message: message,
|
||||||
buttonTitle: NSLocalizedString("Abort", comment: ""))
|
buttonTitle: NSLocalizedString("Abort", comment: "")
|
||||||
|
)
|
||||||
endAppWithDelay()
|
endAppWithDelay()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -267,11 +279,14 @@ class AppDelegate: NSWindowController, NSApplicationDelegate {
|
||||||
if inputSource == nil {
|
if inputSource == nil {
|
||||||
let message = String(
|
let message = String(
|
||||||
format: NSLocalizedString(
|
format: NSLocalizedString(
|
||||||
"Cannot find input source %@ after registration.", comment: ""),
|
"Cannot find input source %@ after registration.", comment: ""
|
||||||
imeIdentifier)
|
),
|
||||||
|
imeIdentifier
|
||||||
|
)
|
||||||
runAlertPanel(
|
runAlertPanel(
|
||||||
title: NSLocalizedString("Fatal Error", comment: ""), message: message,
|
title: NSLocalizedString("Fatal Error", comment: ""), message: message,
|
||||||
buttonTitle: NSLocalizedString("Abort", comment: ""))
|
buttonTitle: NSLocalizedString("Abort", comment: "")
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -304,20 +319,24 @@ class AppDelegate: NSWindowController, NSApplicationDelegate {
|
||||||
ntfPostInstall.messageText = NSLocalizedString("Attention", comment: "")
|
ntfPostInstall.messageText = NSLocalizedString("Attention", comment: "")
|
||||||
ntfPostInstall.informativeText = NSLocalizedString(
|
ntfPostInstall.informativeText = NSLocalizedString(
|
||||||
"vChewing is upgraded, but please log out or reboot for the new version to be fully functional.",
|
"vChewing is upgraded, but please log out or reboot for the new version to be fully functional.",
|
||||||
comment: "")
|
comment: ""
|
||||||
|
)
|
||||||
ntfPostInstall.addButton(withTitle: NSLocalizedString("OK", comment: ""))
|
ntfPostInstall.addButton(withTitle: NSLocalizedString("OK", comment: ""))
|
||||||
} else {
|
} else {
|
||||||
if !mainInputSourceEnabled && !isMacOS12OrAbove {
|
if !mainInputSourceEnabled, !isMacOS12OrAbove {
|
||||||
ntfPostInstall.messageText = NSLocalizedString("Warning", comment: "")
|
ntfPostInstall.messageText = NSLocalizedString("Warning", comment: "")
|
||||||
ntfPostInstall.informativeText = NSLocalizedString(
|
ntfPostInstall.informativeText = NSLocalizedString(
|
||||||
"Input method may not be fully enabled. Please enable it through System Preferences > Keyboard > Input Sources.",
|
"Input method may not be fully enabled. Please enable it through System Preferences > Keyboard > Input Sources.",
|
||||||
comment: "")
|
comment: ""
|
||||||
|
)
|
||||||
ntfPostInstall.addButton(withTitle: NSLocalizedString("Continue", comment: ""))
|
ntfPostInstall.addButton(withTitle: NSLocalizedString("Continue", comment: ""))
|
||||||
} else {
|
} else {
|
||||||
ntfPostInstall.messageText = NSLocalizedString(
|
ntfPostInstall.messageText = NSLocalizedString(
|
||||||
"Installation Successful", comment: "")
|
"Installation Successful", comment: ""
|
||||||
|
)
|
||||||
ntfPostInstall.informativeText = NSLocalizedString(
|
ntfPostInstall.informativeText = NSLocalizedString(
|
||||||
"vChewing is ready to use.", comment: "")
|
"vChewing is ready to use.", comment: ""
|
||||||
|
)
|
||||||
ntfPostInstall.addButton(withTitle: NSLocalizedString("OK", comment: ""))
|
ntfPostInstall.addButton(withTitle: NSLocalizedString("OK", comment: ""))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -332,12 +351,11 @@ class AppDelegate: NSWindowController, NSApplicationDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func cancelAction(_ sender: AnyObject) {
|
@IBAction func cancelAction(_: AnyObject) {
|
||||||
NSApp.terminate(self)
|
NSApp.terminate(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
func windowWillClose(_ notification: Notification) {
|
func windowWillClose(_: Notification) {
|
||||||
NSApp.terminate(self)
|
NSApp.terminate(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,8 @@ struct ArchiveUtil {
|
||||||
let notarizedArchiveExists = FileManager.default.fileExists(atPath: notarizedArchive)
|
let notarizedArchiveExists = FileManager.default.fileExists(atPath: notarizedArchive)
|
||||||
let devModeAppBundleExists = FileManager.default.fileExists(atPath: devModeAppBundlePath)
|
let devModeAppBundleExists = FileManager.default.fileExists(atPath: devModeAppBundlePath)
|
||||||
|
|
||||||
if count > 0 {
|
if !notarizedArchivesContent.isEmpty {
|
||||||
|
// 這裡不用「count > 0」,因為該整數變數只要「!isEmpty」那就必定滿足這個條件。
|
||||||
if count != 1 || !notarizedArchiveExists || devModeAppBundleExists {
|
if count != 1 || !notarizedArchiveExists || devModeAppBundleExists {
|
||||||
let alert = NSAlert()
|
let alert = NSAlert()
|
||||||
alert.alertStyle = .informational
|
alert.alertStyle = .informational
|
||||||
|
@ -105,7 +106,8 @@ struct ArchiveUtil {
|
||||||
let result = (tempFilePath as NSString).appendingPathComponent(targetAppBundleName)
|
let result = (tempFilePath as NSString).appendingPathComponent(targetAppBundleName)
|
||||||
assert(
|
assert(
|
||||||
FileManager.default.fileExists(atPath: result),
|
FileManager.default.fileExists(atPath: result),
|
||||||
"App bundle must be unzipped at \(result).")
|
"App bundle must be unzipped at \(result)."
|
||||||
|
)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,5 +132,4 @@ struct ArchiveUtil {
|
||||||
notarizedArchiveBasename)
|
notarizedArchiveBasename)
|
||||||
return notarizedArchive
|
return notarizedArchive
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,8 @@ let package = Package(
|
||||||
products: [
|
products: [
|
||||||
.library(
|
.library(
|
||||||
name: "OpenCC",
|
name: "OpenCC",
|
||||||
targets: ["OpenCC"])
|
targets: ["OpenCC"]
|
||||||
|
)
|
||||||
],
|
],
|
||||||
targets: [
|
targets: [
|
||||||
.target(
|
.target(
|
||||||
|
@ -15,14 +16,16 @@ let package = Package(
|
||||||
dependencies: ["copencc"],
|
dependencies: ["copencc"],
|
||||||
resources: [
|
resources: [
|
||||||
.copy("Dictionary")
|
.copy("Dictionary")
|
||||||
]),
|
]
|
||||||
|
),
|
||||||
.testTarget(
|
.testTarget(
|
||||||
name: "OpenCCTests",
|
name: "OpenCCTests",
|
||||||
dependencies: ["OpenCC"],
|
dependencies: ["OpenCC"],
|
||||||
resources: [
|
resources: [
|
||||||
.copy("benchmark"),
|
.copy("benchmark"),
|
||||||
.copy("testcases"),
|
.copy("testcases"),
|
||||||
]),
|
]
|
||||||
|
),
|
||||||
.target(
|
.target(
|
||||||
name: "copencc",
|
name: "copencc",
|
||||||
exclude: [
|
exclude: [
|
||||||
|
@ -79,7 +82,8 @@ let package = Package(
|
||||||
.headerSearchPath("deps/marisa-0.2.6/include"),
|
.headerSearchPath("deps/marisa-0.2.6/include"),
|
||||||
.headerSearchPath("deps/marisa-0.2.6/lib"),
|
.headerSearchPath("deps/marisa-0.2.6/lib"),
|
||||||
.define("ENABLE_DARTS"),
|
.define("ENABLE_DARTS"),
|
||||||
]),
|
]
|
||||||
|
),
|
||||||
],
|
],
|
||||||
cxxLanguageStandard: .cxx14
|
cxxLanguageStandard: .cxx14
|
||||||
)
|
)
|
||||||
|
|
|
@ -22,10 +22,8 @@ import copencc
|
||||||
/// However, the string on which it is operating should not be mutated
|
/// However, the string on which it is operating should not be mutated
|
||||||
/// during the course of a conversion.
|
/// during the course of a conversion.
|
||||||
public class ChineseConverter {
|
public class ChineseConverter {
|
||||||
|
|
||||||
/// These constants define the ChineseConverter options.
|
/// These constants define the ChineseConverter options.
|
||||||
public struct Options: OptionSet {
|
public struct Options: OptionSet {
|
||||||
|
|
||||||
public let rawValue: Int
|
public let rawValue: Int
|
||||||
|
|
||||||
public init(rawValue: Int) {
|
public init(rawValue: Int) {
|
||||||
|
@ -62,7 +60,7 @@ public class ChineseConverter {
|
||||||
private init(loader: DictionaryLoader, options: Options) throws {
|
private init(loader: DictionaryLoader, options: Options) throws {
|
||||||
seg = try loader.segmentation(options: options)
|
seg = try loader.segmentation(options: options)
|
||||||
chain = try loader.conversionChain(options: options)
|
chain = try loader.conversionChain(options: options)
|
||||||
var rawChain = chain.map { $0.dict }
|
var rawChain = chain.map(\.dict)
|
||||||
converter = CCConverterCreate("SwiftyOpenCC", seg.dict, &rawChain, rawChain.count)
|
converter = CCConverterCreate("SwiftyOpenCC", seg.dict, &rawChain, rawChain.count)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,5 +83,4 @@ public class ChineseConverter {
|
||||||
defer { STLStringDestroy(stlStr) }
|
defer { STLStringDestroy(stlStr) }
|
||||||
return String(utf8String: STLStringGetUTF8String(stlStr))!
|
return String(utf8String: STLStringGetUTF8String(stlStr))!
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,6 @@ import Foundation
|
||||||
import copencc
|
import copencc
|
||||||
|
|
||||||
class ConversionDictionary {
|
class ConversionDictionary {
|
||||||
|
|
||||||
let group: [ConversionDictionary]
|
let group: [ConversionDictionary]
|
||||||
|
|
||||||
let dict: CCDictRef
|
let dict: CCDictRef
|
||||||
|
@ -23,7 +22,7 @@ class ConversionDictionary {
|
||||||
}
|
}
|
||||||
|
|
||||||
init(group: [ConversionDictionary]) {
|
init(group: [ConversionDictionary]) {
|
||||||
var rawGroup = group.map { $0.dict }
|
var rawGroup = group.map(\.dict)
|
||||||
self.group = group
|
self.group = group
|
||||||
dict = CCDictCreateWithGroup(&rawGroup, rawGroup.count)
|
dict = CCDictCreateWithGroup(&rawGroup, rawGroup.count)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,6 @@ import Foundation
|
||||||
import copencc
|
import copencc
|
||||||
|
|
||||||
public enum ConversionError: Error {
|
public enum ConversionError: Error {
|
||||||
|
|
||||||
case fileNotFound
|
case fileNotFound
|
||||||
|
|
||||||
case invalidFormat
|
case invalidFormat
|
||||||
|
|
|
@ -9,9 +9,7 @@ import Foundation
|
||||||
import copencc
|
import copencc
|
||||||
|
|
||||||
extension ChineseConverter {
|
extension ChineseConverter {
|
||||||
|
|
||||||
struct DictionaryLoader {
|
struct DictionaryLoader {
|
||||||
|
|
||||||
private static let subdirectory = "Dictionary"
|
private static let subdirectory = "Dictionary"
|
||||||
private static let dictCache = WeakValueCache<String, ConversionDictionary>()
|
private static let dictCache = WeakValueCache<String, ConversionDictionary>()
|
||||||
|
|
||||||
|
@ -25,7 +23,8 @@ extension ChineseConverter {
|
||||||
guard
|
guard
|
||||||
let path = bundle.path(
|
let path = bundle.path(
|
||||||
forResource: name.description, ofType: "ocd2",
|
forResource: name.description, ofType: "ocd2",
|
||||||
inDirectory: DictionaryLoader.subdirectory)
|
inDirectory: DictionaryLoader.subdirectory
|
||||||
|
)
|
||||||
else {
|
else {
|
||||||
throw ConversionError.fileNotFound
|
throw ConversionError.fileNotFound
|
||||||
}
|
}
|
||||||
|
@ -37,7 +36,6 @@ extension ChineseConverter {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ChineseConverter.DictionaryLoader {
|
extension ChineseConverter.DictionaryLoader {
|
||||||
|
|
||||||
func segmentation(options: ChineseConverter.Options) throws -> ConversionDictionary {
|
func segmentation(options: ChineseConverter.Options) throws -> ConversionDictionary {
|
||||||
let dictName = options.segmentationDictName
|
let dictName = options.segmentationDictName
|
||||||
return try dict(dictName)
|
return try dict(dictName)
|
||||||
|
|
|
@ -8,9 +8,7 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
extension ChineseConverter {
|
extension ChineseConverter {
|
||||||
|
|
||||||
enum DictionaryName: CustomStringConvertible {
|
enum DictionaryName: CustomStringConvertible {
|
||||||
|
|
||||||
case hkVariants
|
case hkVariants
|
||||||
case hkVariantsRev
|
case hkVariantsRev
|
||||||
case hkVariantsRevPhrases
|
case hkVariantsRevPhrases
|
||||||
|
@ -46,7 +44,6 @@ extension ChineseConverter {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ChineseConverter.Options {
|
extension ChineseConverter.Options {
|
||||||
|
|
||||||
var segmentationDictName: ChineseConverter.DictionaryName {
|
var segmentationDictName: ChineseConverter.DictionaryName {
|
||||||
if contains(.traditionalize) {
|
if contains(.traditionalize) {
|
||||||
return .stPhrases
|
return .stPhrases
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class WeakBox<Value: AnyObject> {
|
class WeakBox<Value: AnyObject> {
|
||||||
|
|
||||||
private(set) weak var value: Value?
|
private(set) weak var value: Value?
|
||||||
|
|
||||||
init(_ value: Value) {
|
init(_ value: Value) {
|
||||||
|
@ -17,7 +16,6 @@ class WeakBox<Value: AnyObject> {
|
||||||
}
|
}
|
||||||
|
|
||||||
class WeakValueCache<Key: Hashable, Value: AnyObject> {
|
class WeakValueCache<Key: Hashable, Value: AnyObject> {
|
||||||
|
|
||||||
private var storage: [Key: WeakBox<Value>] = [:]
|
private var storage: [Key: WeakBox<Value>] = [:]
|
||||||
|
|
||||||
private var lock = NSLock()
|
private var lock = NSLock()
|
||||||
|
|
|
@ -14,7 +14,6 @@ let testCases: [(String, ChineseConverter.Options)] = [
|
||||||
]
|
]
|
||||||
|
|
||||||
class OpenCCTests: XCTestCase {
|
class OpenCCTests: XCTestCase {
|
||||||
|
|
||||||
func converter(option: ChineseConverter.Options) throws -> ChineseConverter {
|
func converter(option: ChineseConverter.Options) throws -> ChineseConverter {
|
||||||
try ChineseConverter(options: option)
|
try ChineseConverter(options: option)
|
||||||
}
|
}
|
||||||
|
@ -22,7 +21,8 @@ class OpenCCTests: XCTestCase {
|
||||||
func testConversion() throws {
|
func testConversion() throws {
|
||||||
func testCase(name: String, ext: String) -> String {
|
func testCase(name: String, ext: String) -> String {
|
||||||
let url = Bundle.module.url(
|
let url = Bundle.module.url(
|
||||||
forResource: name, withExtension: ext, subdirectory: "testcases")!
|
forResource: name, withExtension: ext, subdirectory: "testcases"
|
||||||
|
)!
|
||||||
return try! String(contentsOf: url)
|
return try! String(contentsOf: url)
|
||||||
}
|
}
|
||||||
for (name, opt) in testCases {
|
for (name, opt) in testCases {
|
||||||
|
@ -47,7 +47,7 @@ class OpenCCTests: XCTestCase {
|
||||||
let options: ChineseConverter.Options = [.traditionalize, .twStandard, .twIdiom]
|
let options: ChineseConverter.Options = [.traditionalize, .twStandard, .twIdiom]
|
||||||
let holder = try! ChineseConverter(options: options)
|
let holder = try! ChineseConverter(options: options)
|
||||||
measure {
|
measure {
|
||||||
for _ in 0..<1_000 {
|
for _ in 0..<1000 {
|
||||||
_ = try! ChineseConverter(options: options)
|
_ = try! ChineseConverter(options: options)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,8 @@ class OpenCCTests: XCTestCase {
|
||||||
func testConversionPerformance() throws {
|
func testConversionPerformance() throws {
|
||||||
let cov = try converter(option: [.traditionalize, .twStandard, .twIdiom])
|
let cov = try converter(option: [.traditionalize, .twStandard, .twIdiom])
|
||||||
let url = Bundle.module.url(
|
let url = Bundle.module.url(
|
||||||
forResource: "zuozhuan", withExtension: "txt", subdirectory: "benchmark")!
|
forResource: "zuozhuan", withExtension: "txt", subdirectory: "benchmark"
|
||||||
|
)!
|
||||||
// 1.9 MB, 624k word
|
// 1.9 MB, 624k word
|
||||||
let str = try String(contentsOf: url)
|
let str = try String(contentsOf: url)
|
||||||
measure {
|
measure {
|
||||||
|
|
|
@ -36,7 +36,7 @@ public class OpenCCBridge: NSObject {
|
||||||
private var simplify: ChineseConverter?
|
private var simplify: ChineseConverter?
|
||||||
private var traditionalize: ChineseConverter?
|
private var traditionalize: ChineseConverter?
|
||||||
|
|
||||||
private override init() {
|
override private init() {
|
||||||
try? simplify = ChineseConverter(options: .simplify)
|
try? simplify = ChineseConverter(options: .simplify)
|
||||||
try? traditionalize = ChineseConverter(options: [.traditionalize, .twStandard])
|
try? traditionalize = ChineseConverter(options: [.traditionalize, .twStandard])
|
||||||
super.init()
|
super.init()
|
||||||
|
|
|
@ -26,7 +26,7 @@ extension Preferences {
|
||||||
Function builder for `Preferences` components used in order to restrict types of child views to be of type `Section`.
|
Function builder for `Preferences` components used in order to restrict types of child views to be of type `Section`.
|
||||||
*/
|
*/
|
||||||
@resultBuilder
|
@resultBuilder
|
||||||
public struct SectionBuilder {
|
public enum SectionBuilder {
|
||||||
public static func buildBlock(_ sections: Section...) -> [Section] {
|
public static func buildBlock(_ sections: Section...) -> [Section] {
|
||||||
sections
|
sections
|
||||||
}
|
}
|
||||||
|
@ -78,7 +78,7 @@ extension Preferences {
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private func viewForSection(_ sections: [Section], index: Int) -> some View {
|
private func viewForSection(_ sections: [Section], index: Int) -> some View {
|
||||||
sections[index]
|
sections[index]
|
||||||
if index != sections.count - 1 && sections[index].bottomDivider {
|
if index != sections.count - 1, sections[index].bottomDivider {
|
||||||
Divider()
|
Divider()
|
||||||
// Strangely doesn't work without width being specified. Probably because of custom alignment.
|
// Strangely doesn't work without width being specified. Probably because of custom alignment.
|
||||||
.frame(width: CGFloat(contentWidth), height: 20)
|
.frame(width: CGFloat(contentWidth), height: 20)
|
||||||
|
|
|
@ -93,7 +93,7 @@ extension Preferences {
|
||||||
|
|
||||||
@available(*, unavailable)
|
@available(*, unavailable)
|
||||||
@objc
|
@objc
|
||||||
dynamic required init?(coder: NSCoder) {
|
dynamic required init?(coder _: NSCoder) {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,7 +112,7 @@ final class PreferencesTabViewController: NSViewController, PreferencesStyleCont
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateWindowTitle(tabIndex: Int) {
|
private func updateWindowTitle(tabIndex _: Int) {
|
||||||
window.title = {
|
window.title = {
|
||||||
// if preferencePanes.count > 1 {
|
// if preferencePanes.count > 1 {
|
||||||
// return preferencePanes[tabIndex].preferencePaneTitle
|
// return preferencePanes[tabIndex].preferencePaneTitle
|
||||||
|
@ -200,7 +200,8 @@ final class PreferencesTabViewController: NSViewController, PreferencesStyleCont
|
||||||
options: options,
|
options: options,
|
||||||
completionHandler: completion
|
completionHandler: completion
|
||||||
)
|
)
|
||||||
}, completionHandler: nil)
|
}, completionHandler: nil
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
super.transition(
|
super.transition(
|
||||||
from: fromViewController,
|
from: fromViewController,
|
||||||
|
@ -234,22 +235,22 @@ final class PreferencesTabViewController: NSViewController, PreferencesStyleCont
|
||||||
}
|
}
|
||||||
|
|
||||||
extension PreferencesTabViewController: NSToolbarDelegate {
|
extension PreferencesTabViewController: NSToolbarDelegate {
|
||||||
func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
|
func toolbarDefaultItemIdentifiers(_: NSToolbar) -> [NSToolbarItem.Identifier] {
|
||||||
toolbarItemIdentifiers
|
toolbarItemIdentifiers
|
||||||
}
|
}
|
||||||
|
|
||||||
func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
|
func toolbarAllowedItemIdentifiers(_: NSToolbar) -> [NSToolbarItem.Identifier] {
|
||||||
toolbarItemIdentifiers
|
toolbarItemIdentifiers
|
||||||
}
|
}
|
||||||
|
|
||||||
func toolbarSelectableItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
|
func toolbarSelectableItemIdentifiers(_: NSToolbar) -> [NSToolbarItem.Identifier] {
|
||||||
style == .segmentedControl ? [] : toolbarItemIdentifiers
|
style == .segmentedControl ? [] : toolbarItemIdentifiers
|
||||||
}
|
}
|
||||||
|
|
||||||
public func toolbar(
|
public func toolbar(
|
||||||
_ toolbar: NSToolbar,
|
_: NSToolbar,
|
||||||
itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier,
|
itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier,
|
||||||
willBeInsertedIntoToolbar flag: Bool
|
willBeInsertedIntoToolbar _: Bool
|
||||||
) -> NSToolbarItem? {
|
) -> NSToolbarItem? {
|
||||||
if itemIdentifier == .flexibleSpace {
|
if itemIdentifier == .flexibleSpace {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -87,12 +87,12 @@ public final class PreferencesWindowController: NSWindowController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(*, unavailable)
|
@available(*, unavailable)
|
||||||
override public init(window: NSWindow?) {
|
override public init(window _: NSWindow?) {
|
||||||
fatalError("init(window:) is not supported, use init(preferences:style:animated:)")
|
fatalError("init(window:) is not supported, use init(preferences:style:animated:)")
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(*, unavailable)
|
@available(*, unavailable)
|
||||||
public required init?(coder: NSCoder) {
|
public required init?(coder _: NSCoder) {
|
||||||
fatalError("init(coder:) is not supported, use init(preferences:style:animated:)")
|
fatalError("init(coder:) is not supported, use init(preferences:style:animated:)")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,7 +140,7 @@ public final class PreferencesWindowController: NSWindowController {
|
||||||
|
|
||||||
extension PreferencesWindowController {
|
extension PreferencesWindowController {
|
||||||
/// Returns the active pane if it responds to the given action.
|
/// Returns the active pane if it responds to the given action.
|
||||||
override public func supplementalTarget(forAction action: Selector, sender: Any?) -> Any? {
|
public override func supplementalTarget(forAction action: Selector, sender: Any?) -> Any? {
|
||||||
if let target = super.supplementalTarget(forAction: action, sender: sender) {
|
if let target = super.supplementalTarget(forAction: action, sender: sender) {
|
||||||
return target
|
return target
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@ final class SegmentedControlStyleViewController: NSViewController, PreferencesSt
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(*, unavailable)
|
@available(*, unavailable)
|
||||||
required init?(coder: NSCoder) {
|
required init?(coder _: NSCoder) {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,11 +36,13 @@ extension NSView {
|
||||||
result.append(
|
result.append(
|
||||||
contentsOf: NSLayoutConstraint.constraints(
|
contentsOf: NSLayoutConstraint.constraints(
|
||||||
withVisualFormat: "H:|-0-[subview]-0-|", options: .directionLeadingToTrailing, metrics: nil,
|
withVisualFormat: "H:|-0-[subview]-0-|", options: .directionLeadingToTrailing, metrics: nil,
|
||||||
views: ["subview": self]))
|
views: ["subview": self]
|
||||||
|
))
|
||||||
result.append(
|
result.append(
|
||||||
contentsOf: NSLayoutConstraint.constraints(
|
contentsOf: NSLayoutConstraint.constraints(
|
||||||
withVisualFormat: "V:|-0-[subview]-0-|", options: .directionLeadingToTrailing, metrics: nil,
|
withVisualFormat: "V:|-0-[subview]-0-|", options: .directionLeadingToTrailing, metrics: nil,
|
||||||
views: ["subview": self]))
|
views: ["subview": self]
|
||||||
|
))
|
||||||
translatesAutoresizingMaskIntoConstraints = false
|
translatesAutoresizingMaskIntoConstraints = false
|
||||||
superview.addConstraints(result)
|
superview.addConstraints(result)
|
||||||
|
|
||||||
|
@ -104,7 +106,7 @@ extension Bundle {
|
||||||
/// A window that allows you to disable all user interactions via `isUserInteractionEnabled`.
|
/// 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.
|
/// Used to avoid breaking animations when the user clicks too fast. Disable user interactions during animations and you're set.
|
||||||
class UserInteractionPausableWindow: NSWindow { // swiftlint:disable:this final_class
|
class UserInteractionPausableWindow: NSWindow {
|
||||||
var isUserInteractionEnabled = true
|
var isUserInteractionEnabled = true
|
||||||
|
|
||||||
override func sendEvent(_ event: NSEvent) {
|
override func sendEvent(_ event: NSEvent) {
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
// MARK: - NSComboBox
|
// MARK: - NSComboBox
|
||||||
|
|
||||||
// Ref: https://stackoverflow.com/a/71058587/4162914
|
// Ref: https://stackoverflow.com/a/71058587/4162914
|
||||||
@available(macOS 11.0, *)
|
@available(macOS 11.0, *)
|
||||||
struct ComboBox: NSViewRepresentable {
|
struct ComboBox: NSViewRepresentable {
|
||||||
|
|
|
@ -31,7 +31,7 @@ import InputMethodKit
|
||||||
class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelegate,
|
class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelegate,
|
||||||
FSEventStreamHelperDelegate
|
FSEventStreamHelperDelegate
|
||||||
{
|
{
|
||||||
func helper(_ helper: FSEventStreamHelper, didReceive events: [FSEventStreamHelper.Event]) {
|
func helper(_: FSEventStreamHelper, didReceive _: [FSEventStreamHelper.Event]) {
|
||||||
// 拖 100ms 再重載,畢竟有些有特殊需求的使用者可能會想使用巨型自訂語彙檔案。
|
// 拖 100ms 再重載,畢竟有些有特殊需求的使用者可能會想使用巨型自訂語彙檔案。
|
||||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) {
|
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) {
|
||||||
if mgrPrefs.shouldAutoReloadUserDataFiles {
|
if mgrPrefs.shouldAutoReloadUserDataFiles {
|
||||||
|
@ -42,14 +42,15 @@ class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelega
|
||||||
|
|
||||||
// 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 weak var window: NSWindow?
|
@IBOutlet var window: NSWindow?
|
||||||
private var ctlPrefWindowInstance: ctlPrefWindow?
|
private var ctlPrefWindowInstance: ctlPrefWindow?
|
||||||
private var ctlAboutWindowInstance: ctlAboutWindow? // New About Window
|
private var ctlAboutWindowInstance: ctlAboutWindow? // New About Window
|
||||||
private var checkTask: URLSessionTask?
|
private var checkTask: URLSessionTask?
|
||||||
private var updateNextStepURL: URL?
|
private var updateNextStepURL: URL?
|
||||||
private var fsStreamHelper = FSEventStreamHelper(
|
private var fsStreamHelper = FSEventStreamHelper(
|
||||||
path: mgrLangModel.dataFolderPath(isDefaultFolder: false),
|
path: mgrLangModel.dataFolderPath(isDefaultFolder: false),
|
||||||
queue: DispatchQueue(label: "vChewing User Phrases"))
|
queue: DispatchQueue(label: "vChewing User Phrases")
|
||||||
|
)
|
||||||
private var currentAlertType: String = ""
|
private var currentAlertType: String = ""
|
||||||
|
|
||||||
// 補上 dealloc
|
// 補上 dealloc
|
||||||
|
@ -62,7 +63,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelega
|
||||||
fsStreamHelper.delegate = nil
|
fsStreamHelper.delegate = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func applicationDidFinishLaunching(_ notification: Notification) {
|
func applicationDidFinishLaunching(_: Notification) {
|
||||||
IME.initLangModels(userOnly: false)
|
IME.initLangModels(userOnly: false)
|
||||||
fsStreamHelper.delegate = self
|
fsStreamHelper.delegate = self
|
||||||
_ = fsStreamHelper.start()
|
_ = fsStreamHelper.start()
|
||||||
|
@ -104,7 +105,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelega
|
||||||
|
|
||||||
@objc(checkForUpdateForced:)
|
@objc(checkForUpdateForced:)
|
||||||
func checkForUpdate(forced: Bool) {
|
func checkForUpdate(forced: Bool) {
|
||||||
|
|
||||||
if checkTask != nil {
|
if checkTask != nil {
|
||||||
// busy
|
// busy
|
||||||
return
|
return
|
||||||
|
@ -137,24 +137,30 @@ class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelega
|
||||||
let content = String(
|
let content = String(
|
||||||
format: NSLocalizedString(
|
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?%@",
|
"You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?%@",
|
||||||
comment: ""),
|
comment: ""
|
||||||
|
),
|
||||||
report.currentShortVersion,
|
report.currentShortVersion,
|
||||||
report.currentVersion,
|
report.currentVersion,
|
||||||
report.remoteShortVersion,
|
report.remoteShortVersion,
|
||||||
report.remoteVersion,
|
report.remoteVersion,
|
||||||
report.versionDescription)
|
report.versionDescription
|
||||||
|
)
|
||||||
IME.prtDebugIntel("vChewingDebug: \(content)")
|
IME.prtDebugIntel("vChewingDebug: \(content)")
|
||||||
currentAlertType = "Update"
|
currentAlertType = "Update"
|
||||||
ctlNonModalAlertWindow.shared.show(
|
ctlNonModalAlertWindow.shared.show(
|
||||||
title: NSLocalizedString(
|
title: NSLocalizedString(
|
||||||
"New Version Available", comment: ""),
|
"New Version Available", comment: ""
|
||||||
|
),
|
||||||
content: content,
|
content: content,
|
||||||
confirmButtonTitle: NSLocalizedString(
|
confirmButtonTitle: NSLocalizedString(
|
||||||
"Visit Website", comment: ""),
|
"Visit Website", comment: ""
|
||||||
|
),
|
||||||
cancelButtonTitle: NSLocalizedString(
|
cancelButtonTitle: NSLocalizedString(
|
||||||
"Not Now", comment: ""),
|
"Not Now", comment: ""
|
||||||
|
),
|
||||||
cancelAsDefault: false,
|
cancelAsDefault: false,
|
||||||
delegate: self)
|
delegate: self
|
||||||
|
)
|
||||||
NSApp.setActivationPolicy(.accessory)
|
NSApp.setActivationPolicy(.accessory)
|
||||||
case .noNeedToUpdate, .ignored:
|
case .noNeedToUpdate, .ignored:
|
||||||
break
|
break
|
||||||
|
@ -163,11 +169,14 @@ class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelega
|
||||||
switch error {
|
switch error {
|
||||||
case VersionUpdateApiError.connectionError(let message):
|
case VersionUpdateApiError.connectionError(let message):
|
||||||
let title = NSLocalizedString(
|
let title = NSLocalizedString(
|
||||||
"Update Check Failed", comment: "")
|
"Update Check Failed", comment: ""
|
||||||
|
)
|
||||||
let content = String(
|
let content = String(
|
||||||
format: NSLocalizedString(
|
format: NSLocalizedString(
|
||||||
"There may be no internet connection or the server failed to respond.\n\nError message: %@",
|
"There may be no internet connection or the server failed to respond.\n\nError message: %@",
|
||||||
comment: ""), message)
|
comment: ""
|
||||||
|
), message
|
||||||
|
)
|
||||||
let buttonTitle = NSLocalizedString("Dismiss", comment: "")
|
let buttonTitle = NSLocalizedString("Dismiss", comment: "")
|
||||||
IME.prtDebugIntel("vChewingDebug: \(content)")
|
IME.prtDebugIntel("vChewingDebug: \(content)")
|
||||||
currentAlertType = "Update"
|
currentAlertType = "Update"
|
||||||
|
@ -175,7 +184,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelega
|
||||||
title: title, content: content,
|
title: title, content: content,
|
||||||
confirmButtonTitle: buttonTitle,
|
confirmButtonTitle: buttonTitle,
|
||||||
cancelButtonTitle: nil,
|
cancelButtonTitle: nil,
|
||||||
cancelAsDefault: false, delegate: nil)
|
cancelAsDefault: false, delegate: nil
|
||||||
|
)
|
||||||
NSApp.setActivationPolicy(.accessory)
|
NSApp.setActivationPolicy(.accessory)
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
|
@ -189,20 +199,23 @@ class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelega
|
||||||
let content = String(
|
let content = String(
|
||||||
format: NSLocalizedString(
|
format: NSLocalizedString(
|
||||||
"This will remove vChewing Input Method from this user account, requiring your confirmation.",
|
"This will remove vChewing Input Method from this user account, requiring your confirmation.",
|
||||||
comment: ""))
|
comment: ""
|
||||||
|
))
|
||||||
ctlNonModalAlertWindow.shared.show(
|
ctlNonModalAlertWindow.shared.show(
|
||||||
title: NSLocalizedString("Uninstallation", comment: ""), content: content,
|
title: NSLocalizedString("Uninstallation", comment: ""), content: content,
|
||||||
confirmButtonTitle: NSLocalizedString("OK", comment: ""),
|
confirmButtonTitle: NSLocalizedString("OK", comment: ""),
|
||||||
cancelButtonTitle: NSLocalizedString("Not Now", comment: ""), cancelAsDefault: false,
|
cancelButtonTitle: NSLocalizedString("Not Now", comment: ""), cancelAsDefault: false,
|
||||||
delegate: self)
|
delegate: self
|
||||||
|
)
|
||||||
NSApp.setActivationPolicy(.accessory)
|
NSApp.setActivationPolicy(.accessory)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ctlNonModalAlertWindowDidConfirm(_ controller: ctlNonModalAlertWindow) {
|
func ctlNonModalAlertWindowDidConfirm(_: ctlNonModalAlertWindow) {
|
||||||
switch currentAlertType {
|
switch currentAlertType {
|
||||||
case "Uninstall":
|
case "Uninstall":
|
||||||
NSWorkspace.shared.openFile(
|
NSWorkspace.shared.openFile(
|
||||||
mgrLangModel.dataFolderPath(isDefaultFolder: true), withApplication: "Finder")
|
mgrLangModel.dataFolderPath(isDefaultFolder: true), withApplication: "Finder"
|
||||||
|
)
|
||||||
IME.uninstall(isSudo: false, selfKill: true)
|
IME.uninstall(isSudo: false, selfKill: true)
|
||||||
case "Update":
|
case "Update":
|
||||||
if let updateNextStepURL = updateNextStepURL {
|
if let updateNextStepURL = updateNextStepURL {
|
||||||
|
@ -214,7 +227,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelega
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ctlNonModalAlertWindowDidCancel(_ controller: ctlNonModalAlertWindow) {
|
func ctlNonModalAlertWindowDidCancel(_: ctlNonModalAlertWindow) {
|
||||||
switch currentAlertType {
|
switch currentAlertType {
|
||||||
case "Update":
|
case "Update":
|
||||||
updateNextStepURL = nil
|
updateNextStepURL = nil
|
||||||
|
@ -224,7 +237,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelega
|
||||||
}
|
}
|
||||||
|
|
||||||
// New About Window
|
// New About Window
|
||||||
@IBAction func about(_ sender: Any) {
|
@IBAction func about(_: Any) {
|
||||||
(NSApp.delegate as? AppDelegate)?.showAbout()
|
(NSApp.delegate as? AppDelegate)?.showAbout()
|
||||||
NSApplication.shared.activate(ignoringOtherApps: true)
|
NSApplication.shared.activate(ignoringOtherApps: true)
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,6 +42,7 @@ class AppleKeyboardConverter: NSObject {
|
||||||
@objc class func isDynamicBasicKeyboardLayoutEnabled() -> Bool {
|
@objc class func isDynamicBasicKeyboardLayoutEnabled() -> Bool {
|
||||||
AppleKeyboardConverter.arrDynamicBasicKeyLayout.contains(mgrPrefs.basicKeyboardLayout)
|
AppleKeyboardConverter.arrDynamicBasicKeyLayout.contains(mgrPrefs.basicKeyboardLayout)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 處理 Apple 注音鍵盤佈局類型。
|
// 處理 Apple 注音鍵盤佈局類型。
|
||||||
@objc class func cnvApple2ABC(_ charCode: UniChar) -> UniChar {
|
@objc class func cnvApple2ABC(_ charCode: UniChar) -> UniChar {
|
||||||
var charCode = charCode
|
var charCode = charCode
|
||||||
|
|
|
@ -58,7 +58,6 @@ import Cocoa
|
||||||
/// - Choosing Candidate: The candidate window is open to let the user to choose
|
/// - Choosing Candidate: The candidate window is open to let the user to choose
|
||||||
/// one among the candidates.
|
/// one among the candidates.
|
||||||
class InputState: NSObject {
|
class InputState: NSObject {
|
||||||
|
|
||||||
/// Represents that the input controller is deactivated.
|
/// Represents that the input controller is deactivated.
|
||||||
@objc(InputStateDeactivated)
|
@objc(InputStateDeactivated)
|
||||||
class Deactivated: InputState {
|
class Deactivated: InputState {
|
||||||
|
@ -89,6 +88,7 @@ class InputState: NSObject {
|
||||||
@objc var composingBuffer: String {
|
@objc var composingBuffer: String {
|
||||||
""
|
""
|
||||||
}
|
}
|
||||||
|
|
||||||
override var description: String {
|
override var description: String {
|
||||||
"<InputState.EmptyIgnoringPreviousState>"
|
"<InputState.EmptyIgnoringPreviousState>"
|
||||||
}
|
}
|
||||||
|
@ -147,7 +147,8 @@ class InputState: NSObject {
|
||||||
attributes: [
|
attributes: [
|
||||||
.underlineStyle: NSUnderlineStyle.single.rawValue,
|
.underlineStyle: NSUnderlineStyle.single.rawValue,
|
||||||
.markedClauseSegment: 0,
|
.markedClauseSegment: 0,
|
||||||
])
|
]
|
||||||
|
)
|
||||||
return attributedSting
|
return attributedSting
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,18 +165,18 @@ class InputState: NSObject {
|
||||||
/// Represents that the user is marking a range in the composing buffer.
|
/// Represents that the user is marking a range in the composing buffer.
|
||||||
@objc(InputStateMarking)
|
@objc(InputStateMarking)
|
||||||
class Marking: NotEmpty {
|
class Marking: NotEmpty {
|
||||||
|
|
||||||
@objc private(set) var markerIndex: UInt
|
@objc private(set) var markerIndex: UInt
|
||||||
@objc private(set) var markedRange: NSRange
|
@objc private(set) var markedRange: NSRange
|
||||||
@objc private var deleteTargetExists = false
|
@objc private var deleteTargetExists = false
|
||||||
@objc var tooltip: String {
|
@objc var tooltip: String {
|
||||||
|
|
||||||
if composingBuffer.count != readings.count {
|
if composingBuffer.count != readings.count {
|
||||||
TooltipController.backgroundColor = NSColor(
|
TooltipController.backgroundColor = NSColor(
|
||||||
red: 0.55, green: 0.00, blue: 0.00, alpha: 1.00)
|
red: 0.55, green: 0.00, blue: 0.00, alpha: 1.00
|
||||||
|
)
|
||||||
TooltipController.textColor = NSColor.white
|
TooltipController.textColor = NSColor.white
|
||||||
return NSLocalizedString(
|
return NSLocalizedString(
|
||||||
"⚠︎ Unhandlable: Chars and Readings in buffer doesn't match.", comment: "")
|
"⚠︎ Unhandlable: Chars and Readings in buffer doesn't match.", comment: ""
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if mgrPrefs.phraseReplacementEnabled {
|
if mgrPrefs.phraseReplacementEnabled {
|
||||||
|
@ -192,21 +193,29 @@ class InputState: NSObject {
|
||||||
let text = (composingBuffer as NSString).substring(with: markedRange)
|
let text = (composingBuffer as NSString).substring(with: markedRange)
|
||||||
if markedRange.length < kMinMarkRangeLength {
|
if markedRange.length < kMinMarkRangeLength {
|
||||||
TooltipController.backgroundColor = NSColor(
|
TooltipController.backgroundColor = NSColor(
|
||||||
red: 0.18, green: 0.18, blue: 0.18, alpha: 1.00)
|
red: 0.18, green: 0.18, blue: 0.18, alpha: 1.00
|
||||||
|
)
|
||||||
TooltipController.textColor = NSColor(
|
TooltipController.textColor = NSColor(
|
||||||
red: 0.86, green: 0.86, blue: 0.86, alpha: 1.00)
|
red: 0.86, green: 0.86, blue: 0.86, alpha: 1.00
|
||||||
|
)
|
||||||
return String(
|
return String(
|
||||||
format: NSLocalizedString(
|
format: NSLocalizedString(
|
||||||
"\"%@\" length must ≥ 2 for a user phrase.", comment: ""), text)
|
"\"%@\" length must ≥ 2 for a user phrase.", comment: ""
|
||||||
|
), text
|
||||||
|
)
|
||||||
} else if markedRange.length > kMaxMarkRangeLength {
|
} else if markedRange.length > kMaxMarkRangeLength {
|
||||||
TooltipController.backgroundColor = NSColor(
|
TooltipController.backgroundColor = NSColor(
|
||||||
red: 0.26, green: 0.16, blue: 0.00, alpha: 1.00)
|
red: 0.26, green: 0.16, blue: 0.00, alpha: 1.00
|
||||||
|
)
|
||||||
TooltipController.textColor = NSColor(
|
TooltipController.textColor = NSColor(
|
||||||
red: 1.00, green: 0.60, blue: 0.00, alpha: 1.00)
|
red: 1.00, green: 0.60, blue: 0.00, alpha: 1.00
|
||||||
|
)
|
||||||
return String(
|
return String(
|
||||||
format: NSLocalizedString(
|
format: NSLocalizedString(
|
||||||
"\"%@\" length should ≤ %d for a user phrase.", comment: ""),
|
"\"%@\" length should ≤ %d for a user phrase.", comment: ""
|
||||||
text, kMaxMarkRangeLength)
|
),
|
||||||
|
text, kMaxMarkRangeLength
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
let (exactBegin, _) = (composingBuffer as NSString).characterIndex(
|
let (exactBegin, _) = (composingBuffer as NSString).characterIndex(
|
||||||
|
@ -216,24 +225,30 @@ class InputState: NSObject {
|
||||||
let selectedReadings = readings[exactBegin..<exactEnd]
|
let selectedReadings = readings[exactBegin..<exactEnd]
|
||||||
let joined = selectedReadings.joined(separator: "-")
|
let joined = selectedReadings.joined(separator: "-")
|
||||||
let exist = mgrLangModel.checkIfUserPhraseExist(
|
let exist = mgrLangModel.checkIfUserPhraseExist(
|
||||||
userPhrase: text, mode: ctlInputMethod.currentKeyHandler.inputMode, key: joined)
|
userPhrase: text, mode: ctlInputMethod.currentKeyHandler.inputMode, key: joined
|
||||||
|
)
|
||||||
if exist {
|
if exist {
|
||||||
deleteTargetExists = exist
|
deleteTargetExists = exist
|
||||||
TooltipController.backgroundColor = NSColor(
|
TooltipController.backgroundColor = NSColor(
|
||||||
red: 0.00, green: 0.18, blue: 0.13, alpha: 1.00)
|
red: 0.00, green: 0.18, blue: 0.13, alpha: 1.00
|
||||||
|
)
|
||||||
TooltipController.textColor = NSColor(
|
TooltipController.textColor = NSColor(
|
||||||
red: 0.00, green: 1.00, blue: 0.74, alpha: 1.00)
|
red: 0.00, green: 1.00, blue: 0.74, alpha: 1.00
|
||||||
|
)
|
||||||
return String(
|
return String(
|
||||||
format: NSLocalizedString(
|
format: NSLocalizedString(
|
||||||
"\"%@\" already exists: ↩ to boost, ⇧⌘↩ to exclude.", comment: ""), text
|
"\"%@\" already exists: ↩ to boost, ⇧⌘↩ to exclude.", comment: ""
|
||||||
|
), text
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
TooltipController.backgroundColor = NSColor(
|
TooltipController.backgroundColor = NSColor(
|
||||||
red: 0.18, green: 0.18, blue: 0.18, alpha: 1.00)
|
red: 0.18, green: 0.18, blue: 0.18, alpha: 1.00
|
||||||
|
)
|
||||||
TooltipController.textColor = NSColor.white
|
TooltipController.textColor = NSColor.white
|
||||||
return String(
|
return String(
|
||||||
format: NSLocalizedString("\"%@\" selected. ↩ to add user phrase.", comment: ""),
|
format: NSLocalizedString("\"%@\" selected. ↩ to add user phrase.", comment: ""),
|
||||||
text)
|
text
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc var tooltipForInputting: String = ""
|
@objc var tooltipForInputting: String = ""
|
||||||
|
@ -256,12 +271,14 @@ class InputState: NSObject {
|
||||||
[
|
[
|
||||||
.underlineStyle: NSUnderlineStyle.single.rawValue,
|
.underlineStyle: NSUnderlineStyle.single.rawValue,
|
||||||
.markedClauseSegment: 0,
|
.markedClauseSegment: 0,
|
||||||
], range: NSRange(location: 0, length: markedRange.location))
|
], range: NSRange(location: 0, length: markedRange.location)
|
||||||
|
)
|
||||||
attributedSting.setAttributes(
|
attributedSting.setAttributes(
|
||||||
[
|
[
|
||||||
.underlineStyle: NSUnderlineStyle.thick.rawValue,
|
.underlineStyle: NSUnderlineStyle.thick.rawValue,
|
||||||
.markedClauseSegment: 1,
|
.markedClauseSegment: 1,
|
||||||
], range: markedRange)
|
], range: markedRange
|
||||||
|
)
|
||||||
attributedSting.setAttributes(
|
attributedSting.setAttributes(
|
||||||
[
|
[
|
||||||
.underlineStyle: NSUnderlineStyle.single.rawValue,
|
.underlineStyle: NSUnderlineStyle.single.rawValue,
|
||||||
|
@ -269,7 +286,9 @@ class InputState: NSObject {
|
||||||
],
|
],
|
||||||
range: NSRange(
|
range: NSRange(
|
||||||
location: end,
|
location: end,
|
||||||
length: (composingBuffer as NSString).length - end))
|
length: (composingBuffer as NSString).length - end
|
||||||
|
)
|
||||||
|
)
|
||||||
return attributedSting
|
return attributedSting
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -297,7 +316,7 @@ class InputState: NSObject {
|
||||||
if markedRange.length > kMaxMarkRangeLength {
|
if markedRange.length > kMaxMarkRangeLength {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if ctlInputMethod.areWeDeleting && !deleteTargetExists {
|
if ctlInputMethod.areWeDeleting, !deleteTargetExists {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return markedRange.length >= kMinMarkRangeLength
|
return markedRange.length >= kMinMarkRangeLength
|
||||||
|
@ -313,7 +332,8 @@ class InputState: NSObject {
|
||||||
let selectedReadings = readings[exactBegin..<exactEnd]
|
let selectedReadings = readings[exactBegin..<exactEnd]
|
||||||
let joined = selectedReadings.joined(separator: "-")
|
let joined = selectedReadings.joined(separator: "-")
|
||||||
return mgrLangModel.checkIfUserPhraseExist(
|
return mgrLangModel.checkIfUserPhraseExist(
|
||||||
userPhrase: text, mode: ctlInputMethod.currentKeyHandler.inputMode, key: joined)
|
userPhrase: text, mode: ctlInputMethod.currentKeyHandler.inputMode, key: joined
|
||||||
|
)
|
||||||
== true
|
== true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -363,7 +383,8 @@ class InputState: NSObject {
|
||||||
attributes: [
|
attributes: [
|
||||||
.underlineStyle: NSUnderlineStyle.single.rawValue,
|
.underlineStyle: NSUnderlineStyle.single.rawValue,
|
||||||
.markedClauseSegment: 0,
|
.markedClauseSegment: 0,
|
||||||
])
|
]
|
||||||
|
)
|
||||||
return attributedSting
|
return attributedSting
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -397,17 +418,17 @@ class InputState: NSObject {
|
||||||
|
|
||||||
@objc init(node: SymbolNode, useVerticalMode: Bool) {
|
@objc init(node: SymbolNode, useVerticalMode: Bool) {
|
||||||
self.node = node
|
self.node = node
|
||||||
let candidates = node.children?.map { $0.title } ?? [String]()
|
let candidates = node.children?.map(\.title) ?? [String]()
|
||||||
super.init(
|
super.init(
|
||||||
composingBuffer: "", cursorIndex: 0, candidates: candidates,
|
composingBuffer: "", cursorIndex: 0, candidates: candidates,
|
||||||
useVerticalMode: useVerticalMode)
|
useVerticalMode: useVerticalMode
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override var description: String {
|
override var description: String {
|
||||||
"<InputState.SymbolTable, candidates:\(candidates), useVerticalMode:\(useVerticalMode), composingBuffer:\(composingBuffer), cursorIndex:\(cursorIndex)>"
|
"<InputState.SymbolTable, candidates:\(candidates), useVerticalMode:\(useVerticalMode), composingBuffer:\(composingBuffer), cursorIndex:\(cursorIndex)>"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class SymbolNode: NSObject {
|
class SymbolNode: NSObject {
|
||||||
|
@ -457,7 +478,7 @@ class SymbolNode: NSObject {
|
||||||
@objc static let catLineSegments = String(
|
@objc static let catLineSegments = String(
|
||||||
format: NSLocalizedString("catLineSegments", comment: ""))
|
format: NSLocalizedString("catLineSegments", comment: ""))
|
||||||
|
|
||||||
@objc static let root: SymbolNode = SymbolNode(
|
@objc static let root: SymbolNode = .init(
|
||||||
"/",
|
"/",
|
||||||
[
|
[
|
||||||
SymbolNode("`"),
|
SymbolNode("`"),
|
||||||
|
@ -465,18 +486,21 @@ class SymbolNode: NSObject {
|
||||||
SymbolNode(catHoriBrackets, symbols: "()「」〔〕{}〈〉『』《》【】﹙﹚﹝﹞﹛﹜"),
|
SymbolNode(catHoriBrackets, symbols: "()「」〔〕{}〈〉『』《》【】﹙﹚﹝﹞﹛﹜"),
|
||||||
SymbolNode(catVertBrackets, symbols: "︵︶﹁﹂︹︺︷︸︿﹀﹃﹄︽︾︻︼"),
|
SymbolNode(catVertBrackets, symbols: "︵︶﹁﹂︹︺︷︸︿﹀﹃﹄︽︾︻︼"),
|
||||||
SymbolNode(
|
SymbolNode(
|
||||||
catGreekLetters, symbols: "αβγδεζηθικλμνξοπρστυφχψωΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ"),
|
catGreekLetters, symbols: "αβγδεζηθικλμνξοπρστυφχψωΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ"
|
||||||
|
),
|
||||||
SymbolNode(catMathSymbols, symbols: "+-×÷=≠≒∞±√<>﹤﹥≦≧∩∪ˇ⊥∠∟⊿㏒㏑∫∮∵∴╳﹢"),
|
SymbolNode(catMathSymbols, symbols: "+-×÷=≠≒∞±√<>﹤﹥≦≧∩∪ˇ⊥∠∟⊿㏒㏑∫∮∵∴╳﹢"),
|
||||||
SymbolNode(catCurrencyUnits, symbols: "$€¥¢£₽₨₩฿₺₮₱₭₴₦৲৳૱௹﷼₹₲₪₡₫៛₵₢₸₤₳₥₠₣₰₧₯₶₷"),
|
SymbolNode(catCurrencyUnits, symbols: "$€¥¢£₽₨₩฿₺₮₱₭₴₦৲৳૱௹﷼₹₲₪₡₫៛₵₢₸₤₳₥₠₣₰₧₯₶₷"),
|
||||||
SymbolNode(catSpecialSymbols, symbols: "↑↓←→↖↗↙↘↺⇧⇩⇦⇨⇄⇆⇅⇵↻◎○●⊕⊙※△▲☆★◇◆□■▽▼§¥〒¢£♀♂↯"),
|
SymbolNode(catSpecialSymbols, symbols: "↑↓←→↖↗↙↘↺⇧⇩⇦⇨⇄⇆⇅⇵↻◎○●⊕⊙※△▲☆★◇◆□■▽▼§¥〒¢£♀♂↯"),
|
||||||
SymbolNode(catUnicodeSymbols, symbols: "♨☀☁☂☃♠♥♣♦♩♪♫♬☺☻"),
|
SymbolNode(catUnicodeSymbols, symbols: "♨☀☁☂☃♠♥♣♦♩♪♫♬☺☻"),
|
||||||
SymbolNode(catCircledKanjis, symbols: "㊟㊞㊚㊛㊊㊋㊌㊍㊎㊏㊐㊑㊒㊓㊔㊕㊖㊗︎㊘㊙︎㊜㊝㊠㊡㊢㊣㊤㊥㊦㊧㊨㊩㊪㊫㊬㊭㊮㊯㊰🈚︎🈯︎"),
|
SymbolNode(catCircledKanjis, symbols: "㊟㊞㊚㊛㊊㊋㊌㊍㊎㊏㊐㊑㊒㊓㊔㊕㊖㊗︎㊘㊙︎㊜㊝㊠㊡㊢㊣㊤㊥㊦㊧㊨㊩㊪㊫㊬㊭㊮㊯㊰🈚︎🈯︎"),
|
||||||
SymbolNode(
|
SymbolNode(
|
||||||
catCircledKataKana, symbols: "㋐㋑㋒㋓㋔㋕㋖㋗㋘㋙㋚㋛㋜㋝㋞㋟㋠㋡㋢㋣㋤㋥㋦㋧㋨㋩㋪㋫㋬㋭㋮㋯㋰㋱㋲㋳㋴㋵㋶㋷㋸㋹㋺㋻㋼㋾"),
|
catCircledKataKana, symbols: "㋐㋑㋒㋓㋔㋕㋖㋗㋘㋙㋚㋛㋜㋝㋞㋟㋠㋡㋢㋣㋤㋥㋦㋧㋨㋩㋪㋫㋬㋭㋮㋯㋰㋱㋲㋳㋴㋵㋶㋷㋸㋹㋺㋻㋼㋾"
|
||||||
|
),
|
||||||
SymbolNode(catBracketKanjis, symbols: "㈪㈫㈬㈭㈮㈯㈰㈱㈲㈳㈴㈵㈶㈷㈸㈹㈺㈻㈼㈽㈾㈿㉀㉁㉂㉃"),
|
SymbolNode(catBracketKanjis, symbols: "㈪㈫㈬㈭㈮㈯㈰㈱㈲㈳㈴㈵㈶㈷㈸㈹㈺㈻㈼㈽㈾㈿㉀㉁㉂㉃"),
|
||||||
SymbolNode(catSingleTableLines, symbols: "├─┼┴┬┤┌┐╞═╪╡│▕└┘╭╮╰╯"),
|
SymbolNode(catSingleTableLines, symbols: "├─┼┴┬┤┌┐╞═╪╡│▕└┘╭╮╰╯"),
|
||||||
SymbolNode(catDoubleTableLines, symbols: "╔╦╗╠═╬╣╓╥╖╒╤╕║╚╩╝╟╫╢╙╨╜╞╪╡╘╧╛"),
|
SymbolNode(catDoubleTableLines, symbols: "╔╦╗╠═╬╣╓╥╖╒╤╕║╚╩╝╟╫╢╙╨╜╞╪╡╘╧╛"),
|
||||||
SymbolNode(catFillingBlocks, symbols: "_ˍ▁▂▃▄▅▆▇█▏▎▍▌▋▊▉◢◣◥◤"),
|
SymbolNode(catFillingBlocks, symbols: "_ˍ▁▂▃▄▅▆▇█▏▎▍▌▋▊▉◢◣◥◤"),
|
||||||
SymbolNode(catLineSegments, symbols: "﹣﹦≡|∣∥–︱—︳╴¯ ̄﹉﹊﹍﹎﹋﹌﹏︴∕﹨╱╲/\"),
|
SymbolNode(catLineSegments, symbols: "﹣﹦≡|∣∥–︱—︳╴¯ ̄﹉﹊﹍﹎﹋﹌﹏︴∕﹨╱╲/\"),
|
||||||
])
|
]
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
|
||||||
// MARK: - § Handle Candidate State.
|
// MARK: - § Handle Candidate State.
|
||||||
|
|
||||||
@objc extension KeyHandler {
|
@objc extension KeyHandler {
|
||||||
func _handleCandidateState(
|
func _handleCandidateState(
|
||||||
_ state: InputState,
|
_ state: InputState,
|
||||||
|
@ -75,7 +76,8 @@ import Cocoa
|
||||||
delegate!.keyHandler(
|
delegate!.keyHandler(
|
||||||
self,
|
self,
|
||||||
didSelectCandidateAt: Int(ctlCandidateCurrent.selectedCandidateIndex),
|
didSelectCandidateAt: Int(ctlCandidateCurrent.selectedCandidateIndex),
|
||||||
ctlCandidate: ctlCandidateCurrent)
|
ctlCandidate: ctlCandidateCurrent
|
||||||
|
)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -232,17 +234,18 @@ import Cocoa
|
||||||
candidates = (state as! InputState.AssociatedPhrases).candidates
|
candidates = (state as! InputState.AssociatedPhrases).candidates
|
||||||
}
|
}
|
||||||
|
|
||||||
if candidates.isEmpty { return false }
|
if candidates.isEmpty {
|
||||||
|
return false
|
||||||
if (input.isEnd || input.emacsKey == vChewingEmacsKey.end) && candidates.count > 0 {
|
} else { // 這裡不用「count > 0」,因為該整數變數只要「!isEmpty」那就必定滿足這個條件。
|
||||||
if ctlCandidateCurrent.selectedCandidateIndex == UInt(candidates.count - 1) {
|
if input.isEnd || input.emacsKey == vChewingEmacsKey.end {
|
||||||
IME.prtDebugIntel("9B69AAAD")
|
if ctlCandidateCurrent.selectedCandidateIndex == UInt(candidates.count - 1) {
|
||||||
errorCallback()
|
IME.prtDebugIntel("9B69AAAD")
|
||||||
} else {
|
errorCallback()
|
||||||
ctlCandidateCurrent.selectedCandidateIndex = UInt(candidates.count - 1)
|
} else {
|
||||||
|
ctlCandidateCurrent.selectedCandidateIndex = UInt(candidates.count - 1)
|
||||||
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if state is InputState.AssociatedPhrases {
|
if state is InputState.AssociatedPhrases {
|
||||||
|
@ -300,7 +303,7 @@ import Cocoa
|
||||||
chkKeyValidity(charCode) || ifLangModelHasUnigrams(forKey: customPunctuation)
|
chkKeyValidity(charCode) || ifLangModelHasUnigrams(forKey: customPunctuation)
|
||||||
|| ifLangModelHasUnigrams(forKey: punctuation)
|
|| ifLangModelHasUnigrams(forKey: punctuation)
|
||||||
|
|
||||||
if !shouldAutoSelectCandidate && input.isUpperCaseASCIILetterKey {
|
if !shouldAutoSelectCandidate, input.isUpperCaseASCIILetterKey {
|
||||||
let letter: String! = String(format: "%@%c", "_letter_", CChar(charCode))
|
let letter: String! = String(format: "%@%c", "_letter_", CChar(charCode))
|
||||||
if ifLangModelHasUnigrams(forKey: letter) { shouldAutoSelectCandidate = true }
|
if ifLangModelHasUnigrams(forKey: letter) { shouldAutoSelectCandidate = true }
|
||||||
}
|
}
|
||||||
|
@ -311,12 +314,14 @@ import Cocoa
|
||||||
delegate!.keyHandler(
|
delegate!.keyHandler(
|
||||||
self,
|
self,
|
||||||
didSelectCandidateAt: Int(candidateIndex),
|
didSelectCandidateAt: Int(candidateIndex),
|
||||||
ctlCandidate: ctlCandidateCurrent)
|
ctlCandidate: ctlCandidateCurrent
|
||||||
|
)
|
||||||
clear()
|
clear()
|
||||||
let empty = InputState.EmptyIgnoringPreviousState()
|
let empty = InputState.EmptyIgnoringPreviousState()
|
||||||
stateCallback(empty)
|
stateCallback(empty)
|
||||||
return handle(
|
return handle(
|
||||||
input: input, state: empty, stateCallback: stateCallback, errorCallback: errorCallback)
|
input: input, state: empty, stateCallback: stateCallback, errorCallback: errorCallback
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ import Cocoa
|
||||||
// Ignore the input if its inputText is empty.
|
// Ignore the input if its inputText is empty.
|
||||||
// Reason: such inputs may be functional key combinations.
|
// Reason: such inputs may be functional key combinations.
|
||||||
|
|
||||||
if (inputText).isEmpty {
|
if inputText.isEmpty {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,6 +56,7 @@ import Cocoa
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Caps Lock processing.
|
// MARK: Caps Lock processing.
|
||||||
|
|
||||||
// If Caps Lock is ON, temporarily disable bopomofo.
|
// If Caps Lock is ON, temporarily disable bopomofo.
|
||||||
// Note: Alphanumerical mode processing.
|
// Note: Alphanumerical mode processing.
|
||||||
if input.isBackSpace || input.isEnter || input.isAbsorbedArrowKey || input.isExtraChooseCandidateKey
|
if input.isBackSpace || input.isEnter || input.isAbsorbedArrowKey || input.isExtraChooseCandidateKey
|
||||||
|
@ -75,7 +76,7 @@ import Cocoa
|
||||||
|
|
||||||
// If ASCII but not printable, don't use insertText:replacementRange:
|
// If ASCII but not printable, don't use insertText:replacementRange:
|
||||||
// Certain apps don't handle non-ASCII char insertions.
|
// Certain apps don't handle non-ASCII char insertions.
|
||||||
if charCode < 0x80 && !isPrintable(charCode) {
|
if charCode < 0x80, !isPrintable(charCode) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,9 +89,10 @@ import Cocoa
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Numeric Pad Processing.
|
// MARK: Numeric Pad Processing.
|
||||||
|
|
||||||
if input.isNumericPad {
|
if input.isNumericPad {
|
||||||
if !input.isLeft && !input.isRight && !input.isDown
|
if !input.isLeft, !input.isRight, !input.isDown,
|
||||||
&& !input.isUp && !input.isSpace && isPrintable(charCode)
|
!input.isUp, !input.isSpace, isPrintable(charCode)
|
||||||
{
|
{
|
||||||
clear()
|
clear()
|
||||||
stateCallback(emptyState)
|
stateCallback(emptyState)
|
||||||
|
@ -102,15 +104,19 @@ import Cocoa
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Handle Candidates.
|
// MARK: Handle Candidates.
|
||||||
|
|
||||||
if state is InputState.ChoosingCandidate {
|
if state is InputState.ChoosingCandidate {
|
||||||
return _handleCandidateState(
|
return _handleCandidateState(
|
||||||
state, input: input, stateCallback: stateCallback, errorCallback: errorCallback)
|
state, input: input, stateCallback: stateCallback, errorCallback: errorCallback
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Handle Associated Phrases.
|
// MARK: Handle Associated Phrases.
|
||||||
|
|
||||||
if state is InputState.AssociatedPhrases {
|
if state is InputState.AssociatedPhrases {
|
||||||
let result = _handleCandidateState(
|
let result = _handleCandidateState(
|
||||||
state, input: input, stateCallback: stateCallback, errorCallback: errorCallback)
|
state, input: input, stateCallback: stateCallback, errorCallback: errorCallback
|
||||||
|
)
|
||||||
if result {
|
if result {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
|
@ -119,13 +125,14 @@ import Cocoa
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Handle Marking.
|
// MARK: Handle Marking.
|
||||||
|
|
||||||
if state is InputState.Marking {
|
if state is InputState.Marking {
|
||||||
let marking = state as! InputState.Marking
|
let marking = state as! InputState.Marking
|
||||||
|
|
||||||
if _handleMarkingState(
|
if _handleMarkingState(
|
||||||
state as! InputState.Marking, input: input, stateCallback: stateCallback,
|
state as! InputState.Marking, input: input, stateCallback: stateCallback,
|
||||||
errorCallback: errorCallback)
|
errorCallback: errorCallback
|
||||||
{
|
) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,7 +141,8 @@ import Cocoa
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Handle BPMF Keys.
|
// MARK: Handle BPMF Keys.
|
||||||
var composeReading: Bool = false
|
|
||||||
|
var composeReading = false
|
||||||
let skipPhoneticHandling = input.isReservedKey || input.isControlHold || input.isOptionHold
|
let skipPhoneticHandling = input.isReservedKey || input.isControlHold || input.isOptionHold
|
||||||
|
|
||||||
// See if Phonetic reading is valid.
|
// See if Phonetic reading is valid.
|
||||||
|
@ -150,7 +158,6 @@ import Cocoa
|
||||||
stateCallback(inputting)
|
stateCallback(inputting)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// See if we have composition if Enter/Space is hit and buffer is not empty.
|
// See if we have composition if Enter/Space is hit and buffer is not empty.
|
||||||
|
@ -187,7 +194,8 @@ import Cocoa
|
||||||
if mgrPrefs.useSCPCTypingMode {
|
if mgrPrefs.useSCPCTypingMode {
|
||||||
let choosingCandidates: InputState.ChoosingCandidate = _buildCandidateState(
|
let choosingCandidates: InputState.ChoosingCandidate = _buildCandidateState(
|
||||||
inputting,
|
inputting,
|
||||||
useVerticalMode: input.useVerticalMode)
|
useVerticalMode: input.useVerticalMode
|
||||||
|
)
|
||||||
if choosingCandidates.candidates.count == 1 {
|
if choosingCandidates.candidates.count == 1 {
|
||||||
clear()
|
clear()
|
||||||
let text: String = choosingCandidates.candidates.first ?? ""
|
let text: String = choosingCandidates.candidates.first ?? ""
|
||||||
|
@ -200,7 +208,8 @@ import Cocoa
|
||||||
let associatedPhrases =
|
let associatedPhrases =
|
||||||
buildAssociatePhraseState(
|
buildAssociatePhraseState(
|
||||||
withKey: text,
|
withKey: text,
|
||||||
useVerticalMode: input.useVerticalMode) as? InputState.AssociatedPhrases
|
useVerticalMode: input.useVerticalMode
|
||||||
|
) as? InputState.AssociatedPhrases
|
||||||
if let associatedPhrases = associatedPhrases {
|
if let associatedPhrases = associatedPhrases {
|
||||||
stateCallback(associatedPhrases)
|
stateCallback(associatedPhrases)
|
||||||
} else {
|
} else {
|
||||||
|
@ -247,7 +256,8 @@ import Cocoa
|
||||||
}
|
}
|
||||||
let choosingCandidates = _buildCandidateState(
|
let choosingCandidates = _buildCandidateState(
|
||||||
state as! InputState.NotEmpty,
|
state as! InputState.NotEmpty,
|
||||||
useVerticalMode: input.useVerticalMode)
|
useVerticalMode: input.useVerticalMode
|
||||||
|
)
|
||||||
stateCallback(choosingCandidates)
|
stateCallback(choosingCandidates)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -255,59 +265,72 @@ import Cocoa
|
||||||
// MARK: -
|
// MARK: -
|
||||||
|
|
||||||
// MARK: Esc
|
// MARK: Esc
|
||||||
|
|
||||||
if input.isESC { return _handleEscWithState(state, stateCallback: stateCallback, errorCallback: errorCallback) }
|
if input.isESC { return _handleEscWithState(state, stateCallback: stateCallback, errorCallback: errorCallback) }
|
||||||
|
|
||||||
// MARK: Cursor backward
|
// MARK: Cursor backward
|
||||||
|
|
||||||
if input.isCursorBackward || input.emacsKey == vChewingEmacsKey.backward {
|
if input.isCursorBackward || input.emacsKey == vChewingEmacsKey.backward {
|
||||||
return _handleBackwardWithState(
|
return _handleBackwardWithState(
|
||||||
state,
|
state,
|
||||||
input: input,
|
input: input,
|
||||||
stateCallback: stateCallback,
|
stateCallback: stateCallback,
|
||||||
errorCallback: errorCallback)
|
errorCallback: errorCallback
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Cursor forward
|
// MARK: Cursor forward
|
||||||
|
|
||||||
if input.isCursorForward || input.emacsKey == vChewingEmacsKey.forward {
|
if input.isCursorForward || input.emacsKey == vChewingEmacsKey.forward {
|
||||||
return _handleForwardWithState(
|
return _handleForwardWithState(
|
||||||
state, input: input, stateCallback: stateCallback, errorCallback: errorCallback)
|
state, input: input, stateCallback: stateCallback, errorCallback: errorCallback
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Home
|
// MARK: Home
|
||||||
|
|
||||||
if input.isHome || input.emacsKey == vChewingEmacsKey.home {
|
if input.isHome || input.emacsKey == vChewingEmacsKey.home {
|
||||||
return _handleHomeWithState(state, stateCallback: stateCallback, errorCallback: errorCallback)
|
return _handleHomeWithState(state, stateCallback: stateCallback, errorCallback: errorCallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: End
|
// MARK: End
|
||||||
|
|
||||||
if input.isEnd || input.emacsKey == vChewingEmacsKey.end {
|
if input.isEnd || input.emacsKey == vChewingEmacsKey.end {
|
||||||
return _handleEndWithState(state, stateCallback: stateCallback, errorCallback: errorCallback)
|
return _handleEndWithState(state, stateCallback: stateCallback, errorCallback: errorCallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Ctrl+PgLf or Shift+PgLf
|
// MARK: Ctrl+PgLf or Shift+PgLf
|
||||||
|
|
||||||
if (input.isControlHold || input.isShiftHold) && (input.isOptionHold && input.isLeft) {
|
if (input.isControlHold || input.isShiftHold) && (input.isOptionHold && input.isLeft) {
|
||||||
return _handleHomeWithState(state, stateCallback: stateCallback, errorCallback: errorCallback)
|
return _handleHomeWithState(state, stateCallback: stateCallback, errorCallback: errorCallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Ctrl+PgRt or Shift+PgRt
|
// MARK: Ctrl+PgRt or Shift+PgRt
|
||||||
|
|
||||||
if (input.isControlHold || input.isShiftHold) && (input.isOptionHold && input.isRight) {
|
if (input.isControlHold || input.isShiftHold) && (input.isOptionHold && input.isRight) {
|
||||||
return _handleEndWithState(state, stateCallback: stateCallback, errorCallback: errorCallback)
|
return _handleEndWithState(state, stateCallback: stateCallback, errorCallback: errorCallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: AbsorbedArrowKey
|
// MARK: AbsorbedArrowKey
|
||||||
|
|
||||||
if input.isAbsorbedArrowKey || input.isExtraChooseCandidateKey || input.isExtraChooseCandidateKeyReverse {
|
if input.isAbsorbedArrowKey || input.isExtraChooseCandidateKey || input.isExtraChooseCandidateKeyReverse {
|
||||||
return _handleAbsorbedArrowKeyWithState(state, stateCallback: stateCallback, errorCallback: errorCallback)
|
return _handleAbsorbedArrowKeyWithState(state, stateCallback: stateCallback, errorCallback: errorCallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Backspace
|
// MARK: Backspace
|
||||||
|
|
||||||
if input.isBackSpace {
|
if input.isBackSpace {
|
||||||
return _handleBackspaceWithState(state, stateCallback: stateCallback, errorCallback: errorCallback)
|
return _handleBackspaceWithState(state, stateCallback: stateCallback, errorCallback: errorCallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Delete
|
// MARK: Delete
|
||||||
|
|
||||||
if input.isDelete || input.emacsKey == vChewingEmacsKey.delete {
|
if input.isDelete || input.emacsKey == vChewingEmacsKey.delete {
|
||||||
return _handleDeleteWithState(state, stateCallback: stateCallback, errorCallback: errorCallback)
|
return _handleDeleteWithState(state, stateCallback: stateCallback, errorCallback: errorCallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Enter
|
// MARK: Enter
|
||||||
|
|
||||||
if input.isEnter {
|
if input.isEnter {
|
||||||
return (input.isCommandHold && input.isControlHold)
|
return (input.isCommandHold && input.isControlHold)
|
||||||
? _handleCtrlCommandEnterWithState(state, stateCallback: stateCallback, errorCallback: errorCallback)
|
? _handleCtrlCommandEnterWithState(state, stateCallback: stateCallback, errorCallback: errorCallback)
|
||||||
|
@ -317,6 +340,7 @@ import Cocoa
|
||||||
// MARK: -
|
// MARK: -
|
||||||
|
|
||||||
// MARK: Punctuation list
|
// MARK: Punctuation list
|
||||||
|
|
||||||
if input.isSymbolMenuPhysicalKey && !input.isShiftHold {
|
if input.isSymbolMenuPhysicalKey && !input.isShiftHold {
|
||||||
if !input.isOptionHold {
|
if !input.isOptionHold {
|
||||||
if ifLangModelHasUnigrams(forKey: "_punctuation_list") {
|
if ifLangModelHasUnigrams(forKey: "_punctuation_list") {
|
||||||
|
@ -349,6 +373,7 @@ import Cocoa
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Punctuation
|
// MARK: Punctuation
|
||||||
|
|
||||||
// if nothing is matched, see if it's a punctuation key for current layout.
|
// if nothing is matched, see if it's a punctuation key for current layout.
|
||||||
|
|
||||||
var punctuationNamePrefix = ""
|
var punctuationNamePrefix = ""
|
||||||
|
@ -373,8 +398,8 @@ import Cocoa
|
||||||
state: state,
|
state: state,
|
||||||
usingVerticalMode: input.useVerticalMode,
|
usingVerticalMode: input.useVerticalMode,
|
||||||
stateCallback: stateCallback,
|
stateCallback: stateCallback,
|
||||||
errorCallback: errorCallback)
|
errorCallback: errorCallback
|
||||||
{
|
) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -387,8 +412,8 @@ import Cocoa
|
||||||
state: state,
|
state: state,
|
||||||
usingVerticalMode: input.useVerticalMode,
|
usingVerticalMode: input.useVerticalMode,
|
||||||
stateCallback: stateCallback,
|
stateCallback: stateCallback,
|
||||||
errorCallback: errorCallback)
|
errorCallback: errorCallback
|
||||||
{
|
) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -400,13 +425,14 @@ import Cocoa
|
||||||
state: state,
|
state: state,
|
||||||
usingVerticalMode: input.useVerticalMode,
|
usingVerticalMode: input.useVerticalMode,
|
||||||
stateCallback: stateCallback,
|
stateCallback: stateCallback,
|
||||||
errorCallback: errorCallback)
|
errorCallback: errorCallback
|
||||||
{
|
) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Still Nothing.
|
// MARK: - Still Nothing.
|
||||||
|
|
||||||
// Still nothing? Then we update the composing buffer.
|
// Still nothing? Then we update the composing buffer.
|
||||||
// Note that some app has strange behavior if we don't do this,
|
// Note that some app has strange behavior if we don't do this,
|
||||||
// "thinking" that the key is not actually consumed.
|
// "thinking" that the key is not actually consumed.
|
||||||
|
|
|
@ -27,10 +27,10 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
|
||||||
// MARK: - § Misc functions.
|
// MARK: - § Misc functions.
|
||||||
@objc extension KeyHandler {
|
|
||||||
|
|
||||||
|
@objc extension KeyHandler {
|
||||||
func getCurrentMandarinParser() -> String {
|
func getCurrentMandarinParser() -> String {
|
||||||
return (mgrPrefs.mandarinParserName + "_")
|
mgrPrefs.mandarinParserName + "_"
|
||||||
}
|
}
|
||||||
|
|
||||||
func _actualCandidateCursorIndex() -> Int {
|
func _actualCandidateCursorIndex() -> Int {
|
||||||
|
|
|
@ -27,9 +27,10 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
|
||||||
// MARK: - § State managements.
|
// MARK: - § State managements.
|
||||||
@objc extension KeyHandler {
|
|
||||||
|
|
||||||
|
@objc extension KeyHandler {
|
||||||
// MARK: - 構築狀態(State Building)
|
// MARK: - 構築狀態(State Building)
|
||||||
|
|
||||||
func buildInputtingState() -> InputState.Inputting {
|
func buildInputtingState() -> InputState.Inputting {
|
||||||
// 觸發資料封裝更新,否則下文拿到的數據會是錯的。
|
// 觸發資料封裝更新,否則下文拿到的數據會是錯的。
|
||||||
packageBufferStateMaterials()
|
packageBufferStateMaterials()
|
||||||
|
@ -44,16 +45,17 @@ import Cocoa
|
||||||
|
|
||||||
// 組建提示文本
|
// 組建提示文本
|
||||||
var tooltip = ""
|
var tooltip = ""
|
||||||
if (resultOfBefore == "") && (resultOfAfter != "") {
|
if resultOfBefore == "", resultOfAfter != "" {
|
||||||
tooltip = String(format: NSLocalizedString("Cursor is after \"%@\".", comment: ""), resultOfAfter)
|
tooltip = String(format: NSLocalizedString("Cursor is after \"%@\".", comment: ""), resultOfAfter)
|
||||||
}
|
}
|
||||||
if (resultOfBefore != "") && (resultOfAfter == "") {
|
if resultOfBefore != "", resultOfAfter == "" {
|
||||||
tooltip = String(format: NSLocalizedString("Cursor is before \"%@\".", comment: ""), resultOfBefore)
|
tooltip = String(format: NSLocalizedString("Cursor is before \"%@\".", comment: ""), resultOfBefore)
|
||||||
}
|
}
|
||||||
if (resultOfBefore != "") && (resultOfAfter != "") {
|
if resultOfBefore != "", resultOfAfter != "" {
|
||||||
tooltip = String(
|
tooltip = String(
|
||||||
format: NSLocalizedString("Cursor is between \"%@\" and \"%@\".", comment: ""),
|
format: NSLocalizedString("Cursor is between \"%@\" and \"%@\".", comment: ""),
|
||||||
resultOfAfter, resultOfBefore)
|
resultOfAfter, resultOfBefore
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 給新狀態安插配置好的提示文本、且送出新狀態
|
// 給新狀態安插配置好的提示文本、且送出新狀態
|
||||||
|
@ -62,6 +64,7 @@ import Cocoa
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 用以生成候選詞數組
|
// MARK: - 用以生成候選詞數組
|
||||||
|
|
||||||
func _buildCandidateState(
|
func _buildCandidateState(
|
||||||
_ currentState: InputState.NotEmpty,
|
_ currentState: InputState.NotEmpty,
|
||||||
useVerticalMode: Bool
|
useVerticalMode: Bool
|
||||||
|
@ -72,18 +75,19 @@ import Cocoa
|
||||||
composingBuffer: currentState.composingBuffer,
|
composingBuffer: currentState.composingBuffer,
|
||||||
cursorIndex: currentState.cursorIndex,
|
cursorIndex: currentState.cursorIndex,
|
||||||
candidates: candidatesArray,
|
candidates: candidatesArray,
|
||||||
useVerticalMode: useVerticalMode)
|
useVerticalMode: useVerticalMode
|
||||||
|
)
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 用以處理就地新增自訂語彙時的行為
|
// MARK: - 用以處理就地新增自訂語彙時的行為
|
||||||
|
|
||||||
func _handleMarkingState(
|
func _handleMarkingState(
|
||||||
_ state: InputState.Marking,
|
_ state: InputState.Marking,
|
||||||
input: keyParser,
|
input: keyParser,
|
||||||
stateCallback: @escaping (InputState) -> Void,
|
stateCallback: @escaping (InputState) -> Void,
|
||||||
errorCallback: @escaping () -> Void
|
errorCallback: @escaping () -> Void
|
||||||
) -> Bool {
|
) -> Bool {
|
||||||
|
|
||||||
if input.isESC {
|
if input.isESC {
|
||||||
let inputting = buildInputtingState()
|
let inputting = buildInputtingState()
|
||||||
stateCallback(inputting)
|
stateCallback(inputting)
|
||||||
|
@ -106,7 +110,7 @@ import Cocoa
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shift + Left
|
// Shift + Left
|
||||||
if (input.isCursorBackward || input.emacsKey == vChewingEmacsKey.backward) && (input.isShiftHold) {
|
if input.isCursorBackward || input.emacsKey == vChewingEmacsKey.backward, input.isShiftHold {
|
||||||
var index = state.markerIndex
|
var index = state.markerIndex
|
||||||
if index > 0 {
|
if index > 0 {
|
||||||
index = UInt((state.composingBuffer as NSString).previousUtf16Position(for: Int(index)))
|
index = UInt((state.composingBuffer as NSString).previousUtf16Position(for: Int(index)))
|
||||||
|
@ -114,7 +118,8 @@ import Cocoa
|
||||||
composingBuffer: state.composingBuffer,
|
composingBuffer: state.composingBuffer,
|
||||||
cursorIndex: state.cursorIndex,
|
cursorIndex: state.cursorIndex,
|
||||||
markerIndex: index,
|
markerIndex: index,
|
||||||
readings: state.readings)
|
readings: state.readings
|
||||||
|
)
|
||||||
marking.tooltipForInputting = state.tooltipForInputting
|
marking.tooltipForInputting = state.tooltipForInputting
|
||||||
|
|
||||||
if marking.markedRange.length == 0 {
|
if marking.markedRange.length == 0 {
|
||||||
|
@ -132,7 +137,7 @@ import Cocoa
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shift + Right
|
// Shift + Right
|
||||||
if (input.isCursorForward || input.emacsKey == vChewingEmacsKey.forward) && (input.isShiftHold) {
|
if input.isCursorForward || input.emacsKey == vChewingEmacsKey.forward, input.isShiftHold {
|
||||||
var index = state.markerIndex
|
var index = state.markerIndex
|
||||||
// 這裡繼續用 NSString 是為了與 Zonble 之前引入的 NSStringUtils 相容。
|
// 這裡繼續用 NSString 是為了與 Zonble 之前引入的 NSStringUtils 相容。
|
||||||
// 不然的話,這行判斷會失敗、引發「9B51408D」錯誤。
|
// 不然的話,這行判斷會失敗、引發「9B51408D」錯誤。
|
||||||
|
@ -142,7 +147,8 @@ import Cocoa
|
||||||
composingBuffer: state.composingBuffer,
|
composingBuffer: state.composingBuffer,
|
||||||
cursorIndex: state.cursorIndex,
|
cursorIndex: state.cursorIndex,
|
||||||
markerIndex: index,
|
markerIndex: index,
|
||||||
readings: state.readings)
|
readings: state.readings
|
||||||
|
)
|
||||||
marking.tooltipForInputting = state.tooltipForInputting
|
marking.tooltipForInputting = state.tooltipForInputting
|
||||||
if marking.markedRange.length == 0 {
|
if marking.markedRange.length == 0 {
|
||||||
let inputting = marking.convertToInputting()
|
let inputting = marking.convertToInputting()
|
||||||
|
@ -161,6 +167,7 @@ import Cocoa
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 標點輸入處理
|
// MARK: - 標點輸入處理
|
||||||
|
|
||||||
func _handlePunctuation(
|
func _handlePunctuation(
|
||||||
_ customPunctuation: String,
|
_ customPunctuation: String,
|
||||||
state: InputState,
|
state: InputState,
|
||||||
|
@ -179,9 +186,10 @@ import Cocoa
|
||||||
inputting.poppedText = poppedText
|
inputting.poppedText = poppedText
|
||||||
stateCallback(inputting)
|
stateCallback(inputting)
|
||||||
|
|
||||||
if mgrPrefs.useSCPCTypingMode && isPhoneticReadingBufferEmpty() {
|
if mgrPrefs.useSCPCTypingMode, isPhoneticReadingBufferEmpty() {
|
||||||
let candidateState = _buildCandidateState(
|
let candidateState = _buildCandidateState(
|
||||||
inputting, useVerticalMode: useVerticalMode)
|
inputting, useVerticalMode: useVerticalMode
|
||||||
|
)
|
||||||
if candidateState.candidates.count == 1 {
|
if candidateState.candidates.count == 1 {
|
||||||
clear()
|
clear()
|
||||||
if let strPoppedText: String = candidateState.candidates.first {
|
if let strPoppedText: String = candidateState.candidates.first {
|
||||||
|
@ -208,10 +216,11 @@ import Cocoa
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Enter 鍵處理
|
// MARK: - Enter 鍵處理
|
||||||
|
|
||||||
@discardableResult func _handleEnterWithState(
|
@discardableResult func _handleEnterWithState(
|
||||||
_ state: InputState,
|
_ state: InputState,
|
||||||
stateCallback: @escaping (InputState) -> Void,
|
stateCallback: @escaping (InputState) -> Void,
|
||||||
errorCallback: @escaping () -> Void
|
errorCallback _: @escaping () -> Void
|
||||||
) -> Bool {
|
) -> Bool {
|
||||||
if !(state is InputState.Inputting) {
|
if !(state is InputState.Inputting) {
|
||||||
return false
|
return false
|
||||||
|
@ -230,10 +239,11 @@ import Cocoa
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - CMD+Enter 鍵處理
|
// MARK: - CMD+Enter 鍵處理
|
||||||
|
|
||||||
func _handleCtrlCommandEnterWithState(
|
func _handleCtrlCommandEnterWithState(
|
||||||
_ state: InputState,
|
_ state: InputState,
|
||||||
stateCallback: @escaping (InputState) -> Void,
|
stateCallback: @escaping (InputState) -> Void,
|
||||||
errorCallback: @escaping () -> Void
|
errorCallback _: @escaping () -> Void
|
||||||
) -> Bool {
|
) -> Bool {
|
||||||
if !(state is InputState.Inputting) {
|
if !(state is InputState.Inputting) {
|
||||||
return false
|
return false
|
||||||
|
@ -255,6 +265,7 @@ import Cocoa
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 處理 Backspace (macOS Delete) 按鍵行為
|
// MARK: - 處理 Backspace (macOS Delete) 按鍵行為
|
||||||
|
|
||||||
func _handleBackspaceWithState(
|
func _handleBackspaceWithState(
|
||||||
_ state: InputState,
|
_ state: InputState,
|
||||||
stateCallback: @escaping (InputState) -> Void,
|
stateCallback: @escaping (InputState) -> Void,
|
||||||
|
@ -278,7 +289,7 @@ import Cocoa
|
||||||
doBackSpaceToPhoneticReadingBuffer()
|
doBackSpaceToPhoneticReadingBuffer()
|
||||||
}
|
}
|
||||||
|
|
||||||
if isPhoneticReadingBufferEmpty() && (getBuilderLength() == 0) {
|
if isPhoneticReadingBufferEmpty(), getBuilderLength() == 0 {
|
||||||
let empty = InputState.EmptyIgnoringPreviousState()
|
let empty = InputState.EmptyIgnoringPreviousState()
|
||||||
stateCallback(empty)
|
stateCallback(empty)
|
||||||
} else {
|
} else {
|
||||||
|
@ -289,6 +300,7 @@ import Cocoa
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 處理 PC Delete (macOS Fn+BackSpace) 按鍵行為
|
// MARK: - 處理 PC Delete (macOS Fn+BackSpace) 按鍵行為
|
||||||
|
|
||||||
func _handleDeleteWithState(
|
func _handleDeleteWithState(
|
||||||
_ state: InputState,
|
_ state: InputState,
|
||||||
stateCallback: @escaping (InputState) -> Void,
|
stateCallback: @escaping (InputState) -> Void,
|
||||||
|
@ -303,7 +315,8 @@ import Cocoa
|
||||||
deleteBuilderReadingAfterCursor()
|
deleteBuilderReadingAfterCursor()
|
||||||
_walk()
|
_walk()
|
||||||
let inputting = buildInputtingState()
|
let inputting = buildInputtingState()
|
||||||
if inputting.composingBuffer.count == 0 {
|
// 這裡不用「count > 0」,因為該整數變數只要「!isEmpty」那就必定滿足這個條件。
|
||||||
|
if !inputting.composingBuffer.isEmpty {
|
||||||
let empty = InputState.EmptyIgnoringPreviousState()
|
let empty = InputState.EmptyIgnoringPreviousState()
|
||||||
stateCallback(empty)
|
stateCallback(empty)
|
||||||
} else {
|
} else {
|
||||||
|
@ -324,6 +337,7 @@ import Cocoa
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 處理與當前文字輸入排版前後方向呈 90 度的那兩個方向鍵的按鍵行為
|
// MARK: - 處理與當前文字輸入排版前後方向呈 90 度的那兩個方向鍵的按鍵行為
|
||||||
|
|
||||||
func _handleAbsorbedArrowKeyWithState(
|
func _handleAbsorbedArrowKeyWithState(
|
||||||
_ state: InputState,
|
_ state: InputState,
|
||||||
stateCallback: @escaping (InputState) -> Void,
|
stateCallback: @escaping (InputState) -> Void,
|
||||||
|
@ -341,6 +355,7 @@ import Cocoa
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 處理 Home 鍵行為
|
// MARK: - 處理 Home 鍵行為
|
||||||
|
|
||||||
func _handleHomeWithState(
|
func _handleHomeWithState(
|
||||||
_ state: InputState,
|
_ state: InputState,
|
||||||
stateCallback: @escaping (InputState) -> Void,
|
stateCallback: @escaping (InputState) -> Void,
|
||||||
|
@ -371,6 +386,7 @@ import Cocoa
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 處理 End 鍵行為
|
// MARK: - 處理 End 鍵行為
|
||||||
|
|
||||||
func _handleEndWithState(
|
func _handleEndWithState(
|
||||||
_ state: InputState,
|
_ state: InputState,
|
||||||
stateCallback: @escaping (InputState) -> Void,
|
stateCallback: @escaping (InputState) -> Void,
|
||||||
|
@ -401,10 +417,11 @@ import Cocoa
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 處理 Esc 鍵行為
|
// MARK: - 處理 Esc 鍵行為
|
||||||
|
|
||||||
func _handleEscWithState(
|
func _handleEscWithState(
|
||||||
_ state: InputState,
|
_ state: InputState,
|
||||||
stateCallback: @escaping (InputState) -> Void,
|
stateCallback: @escaping (InputState) -> Void,
|
||||||
errorCallback: @escaping () -> Void
|
errorCallback _: @escaping () -> Void
|
||||||
) -> Bool {
|
) -> Bool {
|
||||||
if !(state is InputState.Inputting) { return false }
|
if !(state is InputState.Inputting) { return false }
|
||||||
|
|
||||||
|
@ -433,14 +450,15 @@ import Cocoa
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 處理向前方向鍵的行為
|
// MARK: - 處理向前方向鍵的行為
|
||||||
|
|
||||||
func _handleForwardWithState(
|
func _handleForwardWithState(
|
||||||
_ state: InputState,
|
_ state: InputState,
|
||||||
input: keyParser,
|
input: keyParser,
|
||||||
stateCallback: @escaping (InputState) -> Void,
|
stateCallback: @escaping (InputState) -> Void,
|
||||||
errorCallback: @escaping () -> Void
|
errorCallback: @escaping () -> Void
|
||||||
) -> Bool {
|
) -> Bool {
|
||||||
|
|
||||||
if !(state is InputState.Inputting) { return false }
|
if !(state is InputState.Inputting) { return false }
|
||||||
|
|
||||||
if !isPhoneticReadingBufferEmpty() {
|
if !isPhoneticReadingBufferEmpty() {
|
||||||
|
@ -461,7 +479,8 @@ import Cocoa
|
||||||
composingBuffer: currentState.composingBuffer,
|
composingBuffer: currentState.composingBuffer,
|
||||||
cursorIndex: currentState.cursorIndex,
|
cursorIndex: currentState.cursorIndex,
|
||||||
markerIndex: UInt(nextPosition),
|
markerIndex: UInt(nextPosition),
|
||||||
readings: _currentReadings())
|
readings: _currentReadings()
|
||||||
|
)
|
||||||
marking.tooltipForInputting = currentState.tooltip
|
marking.tooltipForInputting = currentState.tooltip
|
||||||
stateCallback(marking)
|
stateCallback(marking)
|
||||||
} else {
|
} else {
|
||||||
|
@ -484,13 +503,13 @@ import Cocoa
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 處理向後方向鍵的行為
|
// MARK: - 處理向後方向鍵的行為
|
||||||
|
|
||||||
func _handleBackwardWithState(
|
func _handleBackwardWithState(
|
||||||
_ state: InputState,
|
_ state: InputState,
|
||||||
input: keyParser,
|
input: keyParser,
|
||||||
stateCallback: @escaping (InputState) -> Void,
|
stateCallback: @escaping (InputState) -> Void,
|
||||||
errorCallback: @escaping () -> Void
|
errorCallback: @escaping () -> Void
|
||||||
) -> Bool {
|
) -> Bool {
|
||||||
|
|
||||||
if !(state is InputState.Inputting) { return false }
|
if !(state is InputState.Inputting) { return false }
|
||||||
|
|
||||||
if !isPhoneticReadingBufferEmpty() {
|
if !isPhoneticReadingBufferEmpty() {
|
||||||
|
@ -511,7 +530,8 @@ import Cocoa
|
||||||
composingBuffer: currentState.composingBuffer,
|
composingBuffer: currentState.composingBuffer,
|
||||||
cursorIndex: currentState.cursorIndex,
|
cursorIndex: currentState.cursorIndex,
|
||||||
markerIndex: UInt(previousPosition),
|
markerIndex: UInt(previousPosition),
|
||||||
readings: _currentReadings())
|
readings: _currentReadings()
|
||||||
|
)
|
||||||
marking.tooltipForInputting = currentState.tooltip
|
marking.tooltipForInputting = currentState.tooltip
|
||||||
stateCallback(marking)
|
stateCallback(marking)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -83,8 +83,8 @@ import Cocoa
|
||||||
}
|
}
|
||||||
|
|
||||||
// CharCodes: https://theasciicode.com.ar/ascii-control-characters/horizontal-tab-ascii-code-9.html
|
// CharCodes: https://theasciicode.com.ar/ascii-control-characters/horizontal-tab-ascii-code-9.html
|
||||||
enum CharCode: UInt /*16*/ {
|
enum CharCode: UInt /* 16 */ {
|
||||||
case yajuusenpai = 114_514_19_19_810_893
|
case yajuusenpai = 114_514_191_191_810_893
|
||||||
// CharCode is not reliable at all. KeyCode is the most appropriate choice due to its accuracy.
|
// 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 ...
|
// KeyCode doesn't give a phuque about the character sent through macOS keyboard layouts ...
|
||||||
// ... but only focuses on which physical key is pressed.
|
// ... but only focuses on which physical key is pressed.
|
||||||
|
@ -121,7 +121,8 @@ class keyParser: NSObject {
|
||||||
self.keyCode = keyCode
|
self.keyCode = keyCode
|
||||||
self.charCode = AppleKeyboardConverter.cnvApple2ABC(charCode)
|
self.charCode = AppleKeyboardConverter.cnvApple2ABC(charCode)
|
||||||
emacsKey = EmacsKeyHelper.detect(
|
emacsKey = EmacsKeyHelper.detect(
|
||||||
charCode: AppleKeyboardConverter.cnvApple2ABC(charCode), flags: flags)
|
charCode: AppleKeyboardConverter.cnvApple2ABC(charCode), flags: flags
|
||||||
|
)
|
||||||
// Define Arrow Keys
|
// Define Arrow Keys
|
||||||
cursorForwardKey = useVerticalMode ? .kDownArrow : .kRightArrow
|
cursorForwardKey = useVerticalMode ? .kDownArrow : .kRightArrow
|
||||||
cursorBackwardKey = useVerticalMode ? .kUpArrow : .kLeftArrow
|
cursorBackwardKey = useVerticalMode ? .kUpArrow : .kLeftArrow
|
||||||
|
@ -141,7 +142,8 @@ class keyParser: NSObject {
|
||||||
isFlagChanged = (event.type == .flagsChanged) ? true : false
|
isFlagChanged = (event.type == .flagsChanged) ? true : false
|
||||||
useVerticalMode = isVerticalMode
|
useVerticalMode = isVerticalMode
|
||||||
let charCode: UInt16 = {
|
let charCode: UInt16 = {
|
||||||
guard let inputText = event.characters, inputText.count > 0 else {
|
// 這裡不用「count > 0」,因為該整數變數只要「!isEmpty」那就必定滿足這個條件。
|
||||||
|
guard let inputText = event.characters, !inputText.isEmpty else {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
let first = inputText[inputText.startIndex].utf16.first!
|
let first = inputText[inputText.startIndex].utf16.first!
|
||||||
|
@ -149,7 +151,8 @@ class keyParser: NSObject {
|
||||||
}()
|
}()
|
||||||
self.charCode = AppleKeyboardConverter.cnvApple2ABC(charCode)
|
self.charCode = AppleKeyboardConverter.cnvApple2ABC(charCode)
|
||||||
emacsKey = EmacsKeyHelper.detect(
|
emacsKey = EmacsKeyHelper.detect(
|
||||||
charCode: AppleKeyboardConverter.cnvApple2ABC(charCode), flags: flags)
|
charCode: AppleKeyboardConverter.cnvApple2ABC(charCode), flags: flags
|
||||||
|
)
|
||||||
// Define Arrow Keys in the same way above.
|
// Define Arrow Keys in the same way above.
|
||||||
cursorForwardKey = useVerticalMode ? .kDownArrow : .kRightArrow
|
cursorForwardKey = useVerticalMode ? .kDownArrow : .kRightArrow
|
||||||
cursorBackwardKey = useVerticalMode ? .kUpArrow : .kLeftArrow
|
cursorBackwardKey = useVerticalMode ? .kUpArrow : .kLeftArrow
|
||||||
|
@ -303,7 +306,6 @@ class keyParser: NSObject {
|
||||||
// 只是必須得與 ![input isShift] 搭配使用才可以(也就是僅判定 Shift 沒被摁下的情形)。
|
// 只是必須得與 ![input isShift] 搭配使用才可以(也就是僅判定 Shift 沒被摁下的情形)。
|
||||||
KeyCode(rawValue: keyCode) == KeyCode.kSymbolMenuPhysicalKey
|
KeyCode(rawValue: keyCode) == KeyCode.kSymbolMenuPhysicalKey
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc enum vChewingEmacsKey: UInt16 {
|
@objc enum vChewingEmacsKey: UInt16 {
|
||||||
|
|
|
@ -27,7 +27,6 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
|
||||||
extension NSString {
|
extension NSString {
|
||||||
|
|
||||||
/// Converts the index in an NSString to the index in a Swift 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
|
/// An Emoji might be compose by more than one UTF-16 code points, however
|
||||||
|
|
|
@ -26,7 +26,7 @@ import Cocoa
|
||||||
|
|
||||||
extension String {
|
extension String {
|
||||||
fileprivate mutating func selfReplace(_ strOf: String, _ strWith: String = "") {
|
fileprivate mutating func selfReplace(_ strOf: String, _ strWith: String = "") {
|
||||||
self = self.replacingOccurrences(of: strOf, with: strWith)
|
self = replacingOccurrences(of: strOf, with: strWith)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,6 @@ public protocol FSEventStreamHelperDelegate: AnyObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
public class FSEventStreamHelper: NSObject {
|
public class FSEventStreamHelper: NSObject {
|
||||||
|
|
||||||
public struct Event {
|
public struct Event {
|
||||||
var path: String
|
var path: String
|
||||||
var flags: FSEventStreamEventFlags
|
var flags: FSEventStreamEventFlags
|
||||||
|
@ -59,7 +58,7 @@ public class FSEventStreamHelper: NSObject {
|
||||||
let stream = FSEventStreamCreate(
|
let stream = FSEventStreamCreate(
|
||||||
nil,
|
nil,
|
||||||
{
|
{
|
||||||
(_, clientCallBackInfo, eventCount, eventPaths, eventFlags, eventIds) in
|
_, clientCallBackInfo, eventCount, eventPaths, eventFlags, eventIds in
|
||||||
let helper = Unmanaged<FSEventStreamHelper>.fromOpaque(clientCallBackInfo!)
|
let helper = Unmanaged<FSEventStreamHelper>.fromOpaque(clientCallBackInfo!)
|
||||||
.takeUnretainedValue()
|
.takeUnretainedValue()
|
||||||
let pathsBase = eventPaths.assumingMemoryBound(to: UnsafePointer<CChar>.self)
|
let pathsBase = eventPaths.assumingMemoryBound(to: UnsafePointer<CChar>.self)
|
||||||
|
@ -70,7 +69,8 @@ public class FSEventStreamHelper: NSObject {
|
||||||
FSEventStreamHelper.Event(
|
FSEventStreamHelper.Event(
|
||||||
path: String(cString: pathsPtr[$0]),
|
path: String(cString: pathsPtr[$0]),
|
||||||
flags: flagsPtr[$0],
|
flags: flagsPtr[$0],
|
||||||
id: eventIDsPtr[$0])
|
id: eventIDsPtr[$0]
|
||||||
|
)
|
||||||
}
|
}
|
||||||
helper.delegate?.helper(helper, didReceive: events)
|
helper.delegate?.helper(helper, didReceive: events)
|
||||||
},
|
},
|
||||||
|
|
|
@ -30,14 +30,17 @@ public class IME: NSObject {
|
||||||
static let dlgOpenPath = NSOpenPanel()
|
static let dlgOpenPath = NSOpenPanel()
|
||||||
|
|
||||||
// MARK: - 開關判定當前應用究竟是?
|
// MARK: - 開關判定當前應用究竟是?
|
||||||
|
|
||||||
@objc static var areWeUsingOurOwnPhraseEditor: Bool = false
|
@objc static var areWeUsingOurOwnPhraseEditor: Bool = false
|
||||||
|
|
||||||
// MARK: - 自 ctlInputMethod 讀取當前輸入法的簡繁體模式
|
// MARK: - 自 ctlInputMethod 讀取當前輸入法的簡繁體模式
|
||||||
|
|
||||||
static func getInputMode() -> InputMode {
|
static func getInputMode() -> InputMode {
|
||||||
ctlInputMethod.currentKeyHandler.inputMode
|
ctlInputMethod.currentKeyHandler.inputMode
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Print debug information to the console.
|
// MARK: - Print debug information to the console.
|
||||||
|
|
||||||
@objc static func prtDebugIntel(_ strPrint: String) {
|
@objc static func prtDebugIntel(_ strPrint: String) {
|
||||||
if mgrPrefs.isDebugModeEnabled {
|
if mgrPrefs.isDebugModeEnabled {
|
||||||
NSLog("vChewingErrorCallback: %@", strPrint)
|
NSLog("vChewingErrorCallback: %@", strPrint)
|
||||||
|
@ -45,11 +48,13 @@ public class IME: NSObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 {
|
@objc static var isSudoMode: Bool {
|
||||||
NSUserName() == "root"
|
NSUserName() == "root"
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Initializing Language Models.
|
// MARK: - Initializing Language Models.
|
||||||
|
|
||||||
@objc static func initLangModels(userOnly: Bool) {
|
@objc static func initLangModels(userOnly: Bool) {
|
||||||
if !userOnly {
|
if !userOnly {
|
||||||
mgrLangModel.loadDataModels() // 這句還是不要砍了。
|
mgrLangModel.loadDataModels() // 這句還是不要砍了。
|
||||||
|
@ -63,6 +68,7 @@ public class IME: NSObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - System Dark Mode Status Detector.
|
// MARK: - System Dark Mode Status Detector.
|
||||||
|
|
||||||
@objc static func isDarkMode() -> Bool {
|
@objc static func isDarkMode() -> Bool {
|
||||||
if #available(macOS 10.15, *) {
|
if #available(macOS 10.15, *) {
|
||||||
let appearanceDescription = NSApplication.shared.effectiveAppearance.debugDescription
|
let appearanceDescription = NSApplication.shared.effectiveAppearance.debugDescription
|
||||||
|
@ -83,6 +89,7 @@ public class IME: NSObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Open a phrase data file.
|
// MARK: - Open a phrase data file.
|
||||||
|
|
||||||
static func openPhraseFile(userFileAt path: String) {
|
static func openPhraseFile(userFileAt path: String) {
|
||||||
func checkIfUserFilesExist() -> Bool {
|
func checkIfUserFilesExist() -> Bool {
|
||||||
if !mgrLangModel.chkUserLMFilesExist(InputMode.imeModeCHS)
|
if !mgrLangModel.chkUserLMFilesExist(InputMode.imeModeCHS)
|
||||||
|
@ -90,12 +97,15 @@ public class IME: NSObject {
|
||||||
{
|
{
|
||||||
let content = String(
|
let content = String(
|
||||||
format: NSLocalizedString(
|
format: NSLocalizedString(
|
||||||
"Please check the permission at \"%@\".", comment: ""),
|
"Please check the permission at \"%@\".", comment: ""
|
||||||
mgrLangModel.dataFolderPath(isDefaultFolder: false))
|
),
|
||||||
|
mgrLangModel.dataFolderPath(isDefaultFolder: false)
|
||||||
|
)
|
||||||
ctlNonModalAlertWindow.shared.show(
|
ctlNonModalAlertWindow.shared.show(
|
||||||
title: NSLocalizedString("Unable to create the user phrase file.", comment: ""),
|
title: NSLocalizedString("Unable to create the user phrase file.", comment: ""),
|
||||||
content: content, confirmButtonTitle: NSLocalizedString("OK", comment: ""),
|
content: content, confirmButtonTitle: NSLocalizedString("OK", comment: ""),
|
||||||
cancelButtonTitle: nil, cancelAsDefault: false, delegate: nil)
|
cancelButtonTitle: nil, cancelAsDefault: false, delegate: nil
|
||||||
|
)
|
||||||
NSApp.setActivationPolicy(.accessory)
|
NSApp.setActivationPolicy(.accessory)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -109,12 +119,14 @@ public class IME: NSObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Trash a file if it exists.
|
// MARK: - Trash a file if it exists.
|
||||||
|
|
||||||
@discardableResult static func trashTargetIfExists(_ path: String) -> Bool {
|
@discardableResult static func trashTargetIfExists(_ path: String) -> Bool {
|
||||||
do {
|
do {
|
||||||
if FileManager.default.fileExists(atPath: path) {
|
if FileManager.default.fileExists(atPath: path) {
|
||||||
// 塞入垃圾桶
|
// 塞入垃圾桶
|
||||||
try FileManager.default.trashItem(
|
try FileManager.default.trashItem(
|
||||||
at: URL(fileURLWithPath: path), resultingItemURL: nil)
|
at: URL(fileURLWithPath: path), resultingItemURL: nil
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
NSLog("Item doesn't exist: \(path)")
|
NSLog("Item doesn't exist: \(path)")
|
||||||
}
|
}
|
||||||
|
@ -126,6 +138,7 @@ public class IME: NSObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Uninstall the input method.
|
// MARK: - Uninstall the input method.
|
||||||
|
|
||||||
@discardableResult static func uninstall(isSudo: Bool = false, selfKill: Bool = true) -> Int32 {
|
@discardableResult static func uninstall(isSudo: Bool = false, selfKill: Bool = true) -> Int32 {
|
||||||
// 輸入法自毀處理。這裡不用「Bundle.main.bundleURL」是為了方便使用者以 sudo 身分來移除被錯誤安裝到系統目錄內的輸入法。
|
// 輸入法自毀處理。這裡不用「Bundle.main.bundleURL」是為了方便使用者以 sudo 身分來移除被錯誤安裝到系統目錄內的輸入法。
|
||||||
guard let bundleID = Bundle.main.bundleIdentifier else {
|
guard let bundleID = Bundle.main.bundleIdentifier else {
|
||||||
|
@ -155,8 +168,8 @@ public class IME: NSObject {
|
||||||
let objFullPath = pathLibrary + pathUnitKeyboardLayouts + objPath
|
let objFullPath = pathLibrary + pathUnitKeyboardLayouts + objPath
|
||||||
if !IME.trashTargetIfExists(objFullPath) { return -1 }
|
if !IME.trashTargetIfExists(objFullPath) { return -1 }
|
||||||
}
|
}
|
||||||
if CommandLine.arguments.count > 2 && CommandLine.arguments[2] == "--all"
|
if CommandLine.arguments.count > 2, CommandLine.arguments[2] == "--all",
|
||||||
&& CommandLine.arguments[1] == "uninstall"
|
CommandLine.arguments[1] == "uninstall"
|
||||||
{
|
{
|
||||||
// 再處理是否需要移除放在預設使用者資料夾內的檔案的情況。
|
// 再處理是否需要移除放在預設使用者資料夾內的檔案的情況。
|
||||||
// 如果使用者有在輸入法偏好設定內將該目錄改到別的地方(而不是用 symbol link)的話,則不處理。
|
// 如果使用者有在輸入法偏好設定內將該目錄改到別的地方(而不是用 symbol link)的話,則不處理。
|
||||||
|
@ -177,6 +190,7 @@ public class IME: NSObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Registering the input method.
|
// MARK: - Registering the input method.
|
||||||
|
|
||||||
@discardableResult static func registerInputMethod() -> Int32 {
|
@discardableResult static func registerInputMethod() -> Int32 {
|
||||||
guard let bundleID = Bundle.main.bundleIdentifier else {
|
guard let bundleID = Bundle.main.bundleIdentifier else {
|
||||||
return -1
|
return -1
|
||||||
|
@ -217,7 +231,7 @@ public class IME: NSObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if CommandLine.arguments.count > 2 && CommandLine.arguments[2] == "--all" {
|
if CommandLine.arguments.count > 2, CommandLine.arguments[2] == "--all" {
|
||||||
let enabled = InputSourceHelper.enableAllInputMode(for: bundleID)
|
let enabled = InputSourceHelper.enableAllInputMode(for: bundleID)
|
||||||
NSLog(
|
NSLog(
|
||||||
enabled
|
enabled
|
||||||
|
@ -228,10 +242,12 @@ public class IME: NSObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 準備枚舉系統內所有的 ASCII 鍵盤佈局
|
// MARK: - 準備枚舉系統內所有的 ASCII 鍵盤佈局
|
||||||
|
|
||||||
struct CarbonKeyboardLayout {
|
struct CarbonKeyboardLayout {
|
||||||
var strName: String = ""
|
var strName: String = ""
|
||||||
var strValue: String = ""
|
var strValue: String = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
static let arrWhitelistedKeyLayoutsASCII: [String] = [
|
static let arrWhitelistedKeyLayoutsASCII: [String] = [
|
||||||
"com.apple.keylayout.ABC",
|
"com.apple.keylayout.ABC",
|
||||||
"com.apple.keylayout.ABC-AZERTY",
|
"com.apple.keylayout.ABC-AZERTY",
|
||||||
|
@ -247,12 +263,14 @@ public class IME: NSObject {
|
||||||
// 提前塞入 macOS 內建的兩款動態鍵盤佈局
|
// 提前塞入 macOS 內建的兩款動態鍵盤佈局
|
||||||
var arrKeyLayouts: [IME.CarbonKeyboardLayout] = []
|
var arrKeyLayouts: [IME.CarbonKeyboardLayout] = []
|
||||||
arrKeyLayouts += [
|
arrKeyLayouts += [
|
||||||
IME.CarbonKeyboardLayout.init(
|
IME.CarbonKeyboardLayout(
|
||||||
strName: NSLocalizedString("Apple Chewing - Dachen", comment: ""),
|
strName: NSLocalizedString("Apple Chewing - Dachen", comment: ""),
|
||||||
strValue: "com.apple.keylayout.ZhuyinBopomofo"),
|
strValue: "com.apple.keylayout.ZhuyinBopomofo"
|
||||||
IME.CarbonKeyboardLayout.init(
|
),
|
||||||
|
IME.CarbonKeyboardLayout(
|
||||||
strName: NSLocalizedString("Apple Chewing - Eten Traditional", comment: ""),
|
strName: NSLocalizedString("Apple Chewing - Eten Traditional", comment: ""),
|
||||||
strValue: "com.apple.keylayout.ZhuyinEten"),
|
strValue: "com.apple.keylayout.ZhuyinEten"
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
// 準備枚舉系統內所有的 ASCII 鍵盤佈局
|
// 準備枚舉系統內所有的 ASCII 鍵盤佈局
|
||||||
|
@ -270,8 +288,8 @@ public class IME: NSObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let ptrASCIICapable = TISGetInputSourceProperty(
|
if let ptrASCIICapable = TISGetInputSourceProperty(
|
||||||
source, kTISPropertyInputSourceIsASCIICapable)
|
source, kTISPropertyInputSourceIsASCIICapable
|
||||||
{
|
) {
|
||||||
let asciiCapable = Unmanaged<CFBoolean>.fromOpaque(ptrASCIICapable)
|
let asciiCapable = Unmanaged<CFBoolean>.fromOpaque(ptrASCIICapable)
|
||||||
.takeUnretainedValue()
|
.takeUnretainedValue()
|
||||||
if asciiCapable != kCFBooleanTrue {
|
if asciiCapable != kCFBooleanTrue {
|
||||||
|
@ -302,13 +320,13 @@ public class IME: NSObject {
|
||||||
|
|
||||||
if sourceID.contains("vChewing") {
|
if sourceID.contains("vChewing") {
|
||||||
arrKeyLayoutsMACV += [
|
arrKeyLayoutsMACV += [
|
||||||
IME.CarbonKeyboardLayout.init(strName: localizedName, strValue: sourceID)
|
IME.CarbonKeyboardLayout(strName: localizedName, strValue: sourceID)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
if IME.arrWhitelistedKeyLayoutsASCII.contains(sourceID) {
|
if IME.arrWhitelistedKeyLayoutsASCII.contains(sourceID) {
|
||||||
arrKeyLayoutsASCII += [
|
arrKeyLayoutsASCII += [
|
||||||
IME.CarbonKeyboardLayout.init(strName: localizedName, strValue: sourceID)
|
IME.CarbonKeyboardLayout(strName: localizedName, strValue: sourceID)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -316,7 +334,6 @@ public class IME: NSObject {
|
||||||
arrKeyLayouts += arrKeyLayoutsASCII
|
arrKeyLayouts += arrKeyLayoutsASCII
|
||||||
return arrKeyLayouts
|
return arrKeyLayouts
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Root Extensions
|
// MARK: - Root Extensions
|
||||||
|
@ -331,6 +348,7 @@ extension RangeReplaceableCollection where Element: Hashable {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Error Extension
|
// MARK: - Error Extension
|
||||||
|
|
||||||
extension String: Error {}
|
extension String: Error {}
|
||||||
extension String: LocalizedError {
|
extension String: LocalizedError {
|
||||||
public var errorDescription: String? {
|
public var errorDescription: String? {
|
||||||
|
@ -339,6 +357,7 @@ extension String: LocalizedError {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Ensuring trailing slash of a string:
|
// MARK: - Ensuring trailing slash of a string:
|
||||||
|
|
||||||
extension String {
|
extension String {
|
||||||
mutating func ensureTrailingSlash() {
|
mutating func ensureTrailingSlash() {
|
||||||
if !hasSuffix("/") {
|
if !hasSuffix("/") {
|
||||||
|
|
|
@ -28,9 +28,8 @@ import Carbon
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
|
||||||
public class InputSourceHelper: NSObject {
|
public class InputSourceHelper: NSObject {
|
||||||
|
|
||||||
@available(*, unavailable)
|
@available(*, unavailable)
|
||||||
public override init() {
|
override public init() {
|
||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,7 +88,7 @@ public class InputSourceHelper: NSObject {
|
||||||
}
|
}
|
||||||
let bundleID = Unmanaged<CFString>.fromOpaque(bundleIDPtr).takeUnretainedValue()
|
let bundleID = Unmanaged<CFString>.fromOpaque(bundleIDPtr).takeUnretainedValue()
|
||||||
if String(bundleID) == inputSourceBundleD {
|
if String(bundleID) == inputSourceBundleD {
|
||||||
let modeEnabled = self.enable(inputSource: source)
|
let modeEnabled = enable(inputSource: source)
|
||||||
if !modeEnabled {
|
if !modeEnabled {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -111,18 +110,16 @@ public class InputSourceHelper: NSObject {
|
||||||
let inputsSourceBundleID = Unmanaged<CFString>.fromOpaque(bundleIDPtr)
|
let inputsSourceBundleID = Unmanaged<CFString>.fromOpaque(bundleIDPtr)
|
||||||
.takeUnretainedValue()
|
.takeUnretainedValue()
|
||||||
let inputsSourceModeID = Unmanaged<CFString>.fromOpaque(modePtr).takeUnretainedValue()
|
let inputsSourceModeID = Unmanaged<CFString>.fromOpaque(modePtr).takeUnretainedValue()
|
||||||
if modeID == String(inputsSourceModeID) && bundleID == String(inputsSourceBundleID) {
|
if modeID == String(inputsSourceModeID), bundleID == String(inputsSourceBundleID) {
|
||||||
let enabled = enable(inputSource: source)
|
let enabled = enable(inputSource: source)
|
||||||
print(
|
print(
|
||||||
"Attempt to enable input source of mode: \(modeID), bundle ID: \(bundleID), result: \(enabled)"
|
"Attempt to enable input source of mode: \(modeID), bundle ID: \(bundleID), result: \(enabled)"
|
||||||
)
|
)
|
||||||
return enabled
|
return enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
print("Failed to find any matching input source of mode: \(modeID), bundle ID: \(bundleID)")
|
print("Failed to find any matching input source of mode: \(modeID), bundle ID: \(bundleID)")
|
||||||
return false
|
return false
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc(disableInputSource:)
|
@objc(disableInputSource:)
|
||||||
|
@ -136,5 +133,4 @@ public class InputSourceHelper: NSObject {
|
||||||
let status = TISRegisterInputSource(url as CFURL)
|
let status = TISRegisterInputSource(url as CFURL)
|
||||||
return status == noErr
|
return status == noErr
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,12 +50,14 @@ enum VersionUpdateApiError: Error, LocalizedError {
|
||||||
return String(
|
return String(
|
||||||
format: NSLocalizedString(
|
format: NSLocalizedString(
|
||||||
"There may be no internet connection or the server failed to respond.\n\nError message: %@",
|
"There may be no internet connection or the server failed to respond.\n\nError message: %@",
|
||||||
comment: ""), message)
|
comment: ""
|
||||||
|
), message
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct VersionUpdateApi {
|
enum VersionUpdateApi {
|
||||||
static let kCheckUpdateAutomatically = "CheckUpdateAutomatically"
|
static let kCheckUpdateAutomatically = "CheckUpdateAutomatically"
|
||||||
static let kNextUpdateCheckDateKey = "NextUpdateCheckDate"
|
static let kNextUpdateCheckDateKey = "NextUpdateCheckDate"
|
||||||
static let kUpdateInfoEndpointKey = "UpdateInfoEndpoint"
|
static let kUpdateInfoEndpointKey = "UpdateInfoEndpoint"
|
||||||
|
@ -75,7 +77,8 @@ struct VersionUpdateApi {
|
||||||
|
|
||||||
let request = URLRequest(
|
let request = URLRequest(
|
||||||
url: updateInfoURL, cachePolicy: .reloadIgnoringLocalCacheData,
|
url: updateInfoURL, cachePolicy: .reloadIgnoringLocalCacheData,
|
||||||
timeoutInterval: kTimeoutInterval)
|
timeoutInterval: kTimeoutInterval
|
||||||
|
)
|
||||||
let task = URLSession.shared.dataTask(with: request) { data, _, error in
|
let task = URLSession.shared.dataTask(with: request) { data, _, error in
|
||||||
if let error = error {
|
if let error = error {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
|
@ -92,7 +95,8 @@ struct VersionUpdateApi {
|
||||||
do {
|
do {
|
||||||
guard
|
guard
|
||||||
let plist = try PropertyListSerialization.propertyList(
|
let plist = try PropertyListSerialization.propertyList(
|
||||||
from: data ?? Data(), options: [], format: nil) as? [AnyHashable: Any],
|
from: data ?? Data(), options: [], format: nil
|
||||||
|
) as? [AnyHashable: Any],
|
||||||
let remoteVersion = plist[kCFBundleVersionKey] as? String,
|
let remoteVersion = plist[kCFBundleVersionKey] as? String,
|
||||||
let infoDict = Bundle.main.infoDictionary
|
let infoDict = Bundle.main.infoDictionary
|
||||||
else {
|
else {
|
||||||
|
@ -109,7 +113,8 @@ struct VersionUpdateApi {
|
||||||
|
|
||||||
let currentVersion = infoDict[kCFBundleVersionKey as String] as? String ?? ""
|
let currentVersion = infoDict[kCFBundleVersionKey as String] as? String ?? ""
|
||||||
let result = currentVersion.compare(
|
let result = currentVersion.compare(
|
||||||
remoteVersion, options: .numeric, range: nil, locale: nil)
|
remoteVersion, options: .numeric, range: nil, locale: nil
|
||||||
|
)
|
||||||
|
|
||||||
if result != .orderedAscending {
|
if result != .orderedAscending {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
|
|
|
@ -38,7 +38,6 @@ extension ctlCandidate {
|
||||||
|
|
||||||
@objc(ctlInputMethod)
|
@objc(ctlInputMethod)
|
||||||
class ctlInputMethod: IMKInputController {
|
class ctlInputMethod: IMKInputController {
|
||||||
|
|
||||||
@objc static let kIMEModeCHS = "org.atelierInmu.inputmethod.vChewing.IMECHS"
|
@objc static let kIMEModeCHS = "org.atelierInmu.inputmethod.vChewing.IMECHS"
|
||||||
@objc static let kIMEModeCHT = "org.atelierInmu.inputmethod.vChewing.IMECHT"
|
@objc static let kIMEModeCHT = "org.atelierInmu.inputmethod.vChewing.IMECHT"
|
||||||
@objc static let kIMEModeNULL = "org.atelierInmu.inputmethod.vChewing.IMENULL"
|
@objc static let kIMEModeNULL = "org.atelierInmu.inputmethod.vChewing.IMENULL"
|
||||||
|
@ -51,15 +50,15 @@ class ctlInputMethod: IMKInputController {
|
||||||
|
|
||||||
private var currentClient: Any?
|
private var currentClient: Any?
|
||||||
|
|
||||||
private var keyHandler: KeyHandler = KeyHandler()
|
private var keyHandler: KeyHandler = .init()
|
||||||
private var state: InputState = InputState.Empty()
|
private var state: InputState = .Empty()
|
||||||
|
|
||||||
// 想讓 keyHandler 能夠被外界調查狀態與參數的話,就得對 keyHandler 做常態處理。
|
// 想讓 keyHandler 能夠被外界調查狀態與參數的話,就得對 keyHandler 做常態處理。
|
||||||
// 這樣 InputState 可以藉由這個 ctlInputMethod 了解到當前的輸入模式是簡體中文還是繁體中文。
|
// 這樣 InputState 可以藉由這個 ctlInputMethod 了解到當前的輸入模式是簡體中文還是繁體中文。
|
||||||
// 然而,要是直接對 keyHandler 做常態處理的話,反而會導致 keyParser 無法協同處理。
|
// 然而,要是直接對 keyHandler 做常態處理的話,反而會導致 keyParser 無法協同處理。
|
||||||
// 所以才需要「currentKeyHandler」這個假 keyHandler。
|
// 所以才需要「currentKeyHandler」這個假 keyHandler。
|
||||||
// 這個「currentKeyHandler」僅用來讓其他模組知道當前的輸入模式是什麼模式,除此之外別無屌用。
|
// 這個「currentKeyHandler」僅用來讓其他模組知道當前的輸入模式是什麼模式,除此之外別無屌用。
|
||||||
static var currentKeyHandler: KeyHandler = KeyHandler()
|
static var currentKeyHandler: KeyHandler = .init()
|
||||||
@objc static var currentInputMode = mgrPrefs.mostRecentInputMode
|
@objc static var currentInputMode = mgrPrefs.mostRecentInputMode
|
||||||
|
|
||||||
// MARK: - Keyboard Layout Specifier
|
// MARK: - Keyboard Layout Specifier
|
||||||
|
@ -85,7 +84,7 @@ class ctlInputMethod: IMKInputController {
|
||||||
func resetKeyHandler() {
|
func resetKeyHandler() {
|
||||||
if let currentClient = currentClient {
|
if let currentClient = currentClient {
|
||||||
keyHandler.clear()
|
keyHandler.clear()
|
||||||
self.handle(state: InputState.Empty(), client: currentClient)
|
handle(state: InputState.Empty(), client: currentClient)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,7 +102,7 @@ class ctlInputMethod: IMKInputController {
|
||||||
if bundleCheckID != Bundle.main.bundleIdentifier {
|
if bundleCheckID != Bundle.main.bundleIdentifier {
|
||||||
// Override the keyboard layout to the basic one.
|
// Override the keyboard layout to the basic one.
|
||||||
setKeyLayout()
|
setKeyLayout()
|
||||||
self.handle(state: .Empty(), client: client)
|
handle(state: .Empty(), client: client)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(NSApp.delegate as? AppDelegate)?.checkForUpdate()
|
(NSApp.delegate as? AppDelegate)?.checkForUpdate()
|
||||||
|
@ -112,11 +111,11 @@ class ctlInputMethod: IMKInputController {
|
||||||
override func deactivateServer(_ client: Any!) {
|
override func deactivateServer(_ client: Any!) {
|
||||||
keyHandler.clear()
|
keyHandler.clear()
|
||||||
currentClient = nil
|
currentClient = nil
|
||||||
self.handle(state: .Empty(), client: client)
|
handle(state: .Empty(), client: client)
|
||||||
self.handle(state: .Deactivated(), client: client)
|
handle(state: .Deactivated(), client: client)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func setValue(_ value: Any!, forTag tag: Int, client: Any!) {
|
override func setValue(_ value: Any!, forTag _: Int, client: Any!) {
|
||||||
var newInputMode = InputMode(rawValue: value as? String ?? InputMode.imeModeNULL.rawValue)
|
var newInputMode = InputMode(rawValue: value as? String ?? InputMode.imeModeNULL.rawValue)
|
||||||
switch newInputMode {
|
switch newInputMode {
|
||||||
case InputMode.imeModeCHS:
|
case InputMode.imeModeCHS:
|
||||||
|
@ -129,7 +128,6 @@ class ctlInputMethod: IMKInputController {
|
||||||
mgrLangModel.loadDataModel(newInputMode)
|
mgrLangModel.loadDataModel(newInputMode)
|
||||||
|
|
||||||
if keyHandler.inputMode != newInputMode {
|
if keyHandler.inputMode != newInputMode {
|
||||||
|
|
||||||
UserDefaults.standard.synchronize()
|
UserDefaults.standard.synchronize()
|
||||||
keyHandler.clear()
|
keyHandler.clear()
|
||||||
keyHandler.inputMode = newInputMode
|
keyHandler.inputMode = newInputMode
|
||||||
|
@ -137,7 +135,7 @@ class ctlInputMethod: IMKInputController {
|
||||||
if bundleCheckID != Bundle.main.bundleIdentifier {
|
if bundleCheckID != Bundle.main.bundleIdentifier {
|
||||||
// Remember to override the keyboard layout again -- treat this as an activate event.
|
// Remember to override the keyboard layout again -- treat this as an activate event.
|
||||||
setKeyLayout()
|
setKeyLayout()
|
||||||
self.handle(state: .Empty(), client: client)
|
handle(state: .Empty(), client: client)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -148,13 +146,12 @@ class ctlInputMethod: IMKInputController {
|
||||||
|
|
||||||
// MARK: - IMKServerInput protocol methods
|
// MARK: - IMKServerInput protocol methods
|
||||||
|
|
||||||
override func recognizedEvents(_ sender: Any!) -> Int {
|
override func recognizedEvents(_: Any!) -> Int {
|
||||||
let events: NSEvent.EventTypeMask = [.keyDown, .flagsChanged]
|
let events: NSEvent.EventTypeMask = [.keyDown, .flagsChanged]
|
||||||
return Int(events.rawValue)
|
return Int(events.rawValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func handle(_ event: NSEvent!, client: Any!) -> Bool {
|
override func handle(_ event: NSEvent!, client: Any!) -> Bool {
|
||||||
|
|
||||||
// 這裡仍舊需要判斷 flags。之前使輸入法狀態卡住無法敲漢字的問題已在 KeyHandler 內修復。
|
// 這裡仍舊需要判斷 flags。之前使輸入法狀態卡住無法敲漢字的問題已在 KeyHandler 內修復。
|
||||||
// 這裡不判斷 flags 的話,用方向鍵前後定位光標之後,再次試圖觸發組字區時、反而會在首次按鍵時失敗。
|
// 這裡不判斷 flags 的話,用方向鍵前後定位光標之後,再次試圖觸發組字區時、反而會在首次按鍵時失敗。
|
||||||
// 同時注意:必須在 event.type == .flagsChanged 結尾插入 return false,
|
// 同時注意:必須在 event.type == .flagsChanged 結尾插入 return false,
|
||||||
|
@ -168,7 +165,8 @@ class ctlInputMethod: IMKInputController {
|
||||||
|
|
||||||
var textFrame = NSRect.zero
|
var textFrame = NSRect.zero
|
||||||
let attributes: [AnyHashable: Any]? = (client as? IMKTextInput)?.attributes(
|
let attributes: [AnyHashable: Any]? = (client as? IMKTextInput)?.attributes(
|
||||||
forCharacterIndex: 0, lineHeightRectangle: &textFrame)
|
forCharacterIndex: 0, lineHeightRectangle: &textFrame
|
||||||
|
)
|
||||||
let useVerticalMode =
|
let useVerticalMode =
|
||||||
(attributes?["IMKTextOrientation"] as? NSNumber)?.intValue == 0 || false
|
(attributes?["IMKTextOrientation"] as? NSNumber)?.intValue == 0 || false
|
||||||
|
|
||||||
|
@ -194,7 +192,6 @@ class ctlInputMethod: IMKInputController {
|
||||||
// MARK: - State Handling
|
// MARK: - State Handling
|
||||||
|
|
||||||
extension ctlInputMethod {
|
extension ctlInputMethod {
|
||||||
|
|
||||||
private func handle(state newState: InputState, client: Any?) {
|
private func handle(state newState: InputState, client: Any?) {
|
||||||
let previous = state
|
let previous = state
|
||||||
state = newState
|
state = newState
|
||||||
|
@ -219,17 +216,16 @@ extension ctlInputMethod {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func commit(text: String, client: Any!) {
|
private func commit(text: String, client: Any!) {
|
||||||
|
|
||||||
func kanjiConversionIfRequired(_ text: String) -> String {
|
func kanjiConversionIfRequired(_ text: String) -> String {
|
||||||
if keyHandler.inputMode == InputMode.imeModeCHT {
|
if keyHandler.inputMode == InputMode.imeModeCHT {
|
||||||
if !mgrPrefs.chineseConversionEnabled && mgrPrefs.shiftJISShinjitaiOutputEnabled {
|
if !mgrPrefs.chineseConversionEnabled, mgrPrefs.shiftJISShinjitaiOutputEnabled {
|
||||||
return vChewingKanjiConverter.cnvTradToJIS(text)
|
return vChewingKanjiConverter.cnvTradToJIS(text)
|
||||||
}
|
}
|
||||||
if mgrPrefs.chineseConversionEnabled && !mgrPrefs.shiftJISShinjitaiOutputEnabled {
|
if mgrPrefs.chineseConversionEnabled, !mgrPrefs.shiftJISShinjitaiOutputEnabled {
|
||||||
return vChewingKanjiConverter.cnvTradToKangXi(text)
|
return vChewingKanjiConverter.cnvTradToKangXi(text)
|
||||||
}
|
}
|
||||||
// 本來這兩個開關不該同時開啟的,但萬一被開啟了的話就這樣處理:
|
// 本來這兩個開關不該同時開啟的,但萬一被開啟了的話就這樣處理:
|
||||||
if mgrPrefs.chineseConversionEnabled && mgrPrefs.shiftJISShinjitaiOutputEnabled {
|
if mgrPrefs.chineseConversionEnabled, mgrPrefs.shiftJISShinjitaiOutputEnabled {
|
||||||
return vChewingKanjiConverter.cnvTradToJIS(text)
|
return vChewingKanjiConverter.cnvTradToJIS(text)
|
||||||
}
|
}
|
||||||
// if (!mgrPrefs.chineseConversionEnabled && !mgrPrefs.shiftJISShinjitaiOutputEnabled) || (keyHandler.inputMode != InputMode.imeModeCHT);
|
// if (!mgrPrefs.chineseConversionEnabled && !mgrPrefs.shiftJISShinjitaiOutputEnabled) || (keyHandler.inputMode != InputMode.imeModeCHT);
|
||||||
|
@ -243,10 +239,11 @@ extension ctlInputMethod {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
(client as? IMKTextInput)?.insertText(
|
(client as? IMKTextInput)?.insertText(
|
||||||
buffer, replacementRange: NSRange(location: NSNotFound, length: NSNotFound))
|
buffer, replacementRange: NSRange(location: NSNotFound, length: NSNotFound)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handle(state: InputState.Deactivated, previous: InputState, client: Any?) {
|
private func handle(state _: InputState.Deactivated, previous: InputState, client: Any?) {
|
||||||
currentClient = nil
|
currentClient = nil
|
||||||
|
|
||||||
ctlCandidateCurrent?.delegate = nil
|
ctlCandidateCurrent?.delegate = nil
|
||||||
|
@ -258,10 +255,11 @@ extension ctlInputMethod {
|
||||||
}
|
}
|
||||||
(client as? IMKTextInput)?.setMarkedText(
|
(client as? IMKTextInput)?.setMarkedText(
|
||||||
"", selectionRange: NSRange(location: 0, length: 0),
|
"", selectionRange: NSRange(location: 0, length: 0),
|
||||||
replacementRange: NSRange(location: NSNotFound, length: NSNotFound))
|
replacementRange: NSRange(location: NSNotFound, length: NSNotFound)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handle(state: InputState.Empty, previous: InputState, client: Any?) {
|
private func handle(state _: InputState.Empty, previous: InputState, client: Any?) {
|
||||||
ctlCandidateCurrent?.visible = false
|
ctlCandidateCurrent?.visible = false
|
||||||
hideTooltip()
|
hideTooltip()
|
||||||
|
|
||||||
|
@ -274,11 +272,12 @@ extension ctlInputMethod {
|
||||||
}
|
}
|
||||||
client.setMarkedText(
|
client.setMarkedText(
|
||||||
"", selectionRange: NSRange(location: 0, length: 0),
|
"", selectionRange: NSRange(location: 0, length: 0),
|
||||||
replacementRange: NSRange(location: NSNotFound, length: NSNotFound))
|
replacementRange: NSRange(location: NSNotFound, length: NSNotFound)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handle(
|
private func handle(
|
||||||
state: InputState.EmptyIgnoringPreviousState, previous: InputState, client: Any!
|
state _: InputState.EmptyIgnoringPreviousState, previous _: InputState, client: Any!
|
||||||
) {
|
) {
|
||||||
ctlCandidateCurrent?.visible = false
|
ctlCandidateCurrent?.visible = false
|
||||||
hideTooltip()
|
hideTooltip()
|
||||||
|
@ -289,10 +288,11 @@ extension ctlInputMethod {
|
||||||
|
|
||||||
client.setMarkedText(
|
client.setMarkedText(
|
||||||
"", selectionRange: NSRange(location: 0, length: 0),
|
"", selectionRange: NSRange(location: 0, length: 0),
|
||||||
replacementRange: NSRange(location: NSNotFound, length: NSNotFound))
|
replacementRange: NSRange(location: NSNotFound, length: NSNotFound)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handle(state: InputState.Committing, previous: InputState, client: Any?) {
|
private func handle(state: InputState.Committing, previous _: InputState, client: Any?) {
|
||||||
ctlCandidateCurrent?.visible = false
|
ctlCandidateCurrent?.visible = false
|
||||||
hideTooltip()
|
hideTooltip()
|
||||||
|
|
||||||
|
@ -306,10 +306,11 @@ extension ctlInputMethod {
|
||||||
}
|
}
|
||||||
client.setMarkedText(
|
client.setMarkedText(
|
||||||
"", selectionRange: NSRange(location: 0, length: 0),
|
"", selectionRange: NSRange(location: 0, length: 0),
|
||||||
replacementRange: NSRange(location: NSNotFound, length: NSNotFound))
|
replacementRange: NSRange(location: NSNotFound, length: NSNotFound)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handle(state: InputState.Inputting, previous: InputState, client: Any?) {
|
private func handle(state: InputState.Inputting, previous _: InputState, client: Any?) {
|
||||||
ctlCandidateCurrent?.visible = false
|
ctlCandidateCurrent?.visible = false
|
||||||
hideTooltip()
|
hideTooltip()
|
||||||
|
|
||||||
|
@ -326,15 +327,17 @@ extension ctlInputMethod {
|
||||||
// i.e. the client app needs to take care of where to put this composing buffer
|
// i.e. the client app needs to take care of where to put this composing buffer
|
||||||
client.setMarkedText(
|
client.setMarkedText(
|
||||||
state.attributedString, selectionRange: NSRange(location: Int(state.cursorIndex), length: 0),
|
state.attributedString, selectionRange: NSRange(location: Int(state.cursorIndex), length: 0),
|
||||||
replacementRange: NSRange(location: NSNotFound, length: NSNotFound))
|
replacementRange: NSRange(location: NSNotFound, length: NSNotFound)
|
||||||
|
)
|
||||||
if !state.tooltip.isEmpty {
|
if !state.tooltip.isEmpty {
|
||||||
show(
|
show(
|
||||||
tooltip: state.tooltip, composingBuffer: state.composingBuffer,
|
tooltip: state.tooltip, composingBuffer: state.composingBuffer,
|
||||||
cursorIndex: state.cursorIndex, client: client)
|
cursorIndex: state.cursorIndex, client: client
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handle(state: InputState.Marking, previous: InputState, client: Any?) {
|
private func handle(state: InputState.Marking, previous _: InputState, client: Any?) {
|
||||||
ctlCandidateCurrent?.visible = false
|
ctlCandidateCurrent?.visible = false
|
||||||
guard let client = client as? IMKTextInput else {
|
guard let client = client as? IMKTextInput else {
|
||||||
hideTooltip()
|
hideTooltip()
|
||||||
|
@ -345,18 +348,20 @@ extension ctlInputMethod {
|
||||||
// i.e. the client app needs to take care of where to put this composing buffer
|
// i.e. the client app needs to take care of where to put this composing buffer
|
||||||
client.setMarkedText(
|
client.setMarkedText(
|
||||||
state.attributedString, selectionRange: NSRange(location: Int(state.cursorIndex), length: 0),
|
state.attributedString, selectionRange: NSRange(location: Int(state.cursorIndex), length: 0),
|
||||||
replacementRange: NSRange(location: NSNotFound, length: NSNotFound))
|
replacementRange: NSRange(location: NSNotFound, length: NSNotFound)
|
||||||
|
)
|
||||||
|
|
||||||
if state.tooltip.isEmpty {
|
if state.tooltip.isEmpty {
|
||||||
hideTooltip()
|
hideTooltip()
|
||||||
} else {
|
} else {
|
||||||
show(
|
show(
|
||||||
tooltip: state.tooltip, composingBuffer: state.composingBuffer,
|
tooltip: state.tooltip, composingBuffer: state.composingBuffer,
|
||||||
cursorIndex: state.markerIndex, client: client)
|
cursorIndex: state.markerIndex, client: client
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handle(state: InputState.ChoosingCandidate, previous: InputState, client: Any?) {
|
private func handle(state: InputState.ChoosingCandidate, previous _: InputState, client: Any?) {
|
||||||
hideTooltip()
|
hideTooltip()
|
||||||
guard let client = client as? IMKTextInput else {
|
guard let client = client as? IMKTextInput else {
|
||||||
ctlCandidateCurrent?.visible = false
|
ctlCandidateCurrent?.visible = false
|
||||||
|
@ -367,11 +372,12 @@ extension ctlInputMethod {
|
||||||
// i.e. the client app needs to take care of where to put this composing buffer
|
// i.e. the client app needs to take care of where to put this composing buffer
|
||||||
client.setMarkedText(
|
client.setMarkedText(
|
||||||
state.attributedString, selectionRange: NSRange(location: Int(state.cursorIndex), length: 0),
|
state.attributedString, selectionRange: NSRange(location: Int(state.cursorIndex), length: 0),
|
||||||
replacementRange: NSRange(location: NSNotFound, length: NSNotFound))
|
replacementRange: NSRange(location: NSNotFound, length: NSNotFound)
|
||||||
|
)
|
||||||
show(candidateWindowWith: state, client: client)
|
show(candidateWindowWith: state, client: client)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handle(state: InputState.AssociatedPhrases, previous: InputState, client: Any?) {
|
private func handle(state: InputState.AssociatedPhrases, previous _: InputState, client: Any?) {
|
||||||
hideTooltip()
|
hideTooltip()
|
||||||
guard let client = client as? IMKTextInput else {
|
guard let client = client as? IMKTextInput else {
|
||||||
ctlCandidateCurrent?.visible = false
|
ctlCandidateCurrent?.visible = false
|
||||||
|
@ -379,7 +385,8 @@ extension ctlInputMethod {
|
||||||
}
|
}
|
||||||
client.setMarkedText(
|
client.setMarkedText(
|
||||||
"", selectionRange: NSRange(location: 0, length: 0),
|
"", selectionRange: NSRange(location: 0, length: 0),
|
||||||
replacementRange: NSRange(location: NSNotFound, length: NSNotFound))
|
replacementRange: NSRange(location: NSNotFound, length: NSNotFound)
|
||||||
|
)
|
||||||
show(candidateWindowWith: state, client: client)
|
show(candidateWindowWith: state, client: client)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -387,7 +394,6 @@ extension ctlInputMethod {
|
||||||
// MARK: -
|
// MARK: -
|
||||||
|
|
||||||
extension ctlInputMethod {
|
extension ctlInputMethod {
|
||||||
|
|
||||||
private func show(candidateWindowWith state: InputState, client: Any!) {
|
private func show(candidateWindowWith state: InputState, client: Any!) {
|
||||||
let useVerticalMode: Bool = {
|
let useVerticalMode: Bool = {
|
||||||
var useVerticalMode = false
|
var useVerticalMode = false
|
||||||
|
@ -449,9 +455,11 @@ extension ctlInputMethod {
|
||||||
}
|
}
|
||||||
|
|
||||||
ctlCandidateCurrent?.keyLabelFont = labelFont(
|
ctlCandidateCurrent?.keyLabelFont = labelFont(
|
||||||
name: mgrPrefs.candidateKeyLabelFontName, size: keyLabelSize)
|
name: mgrPrefs.candidateKeyLabelFontName, size: keyLabelSize
|
||||||
|
)
|
||||||
ctlCandidateCurrent?.candidateFont = candidateFont(
|
ctlCandidateCurrent?.candidateFont = candidateFont(
|
||||||
name: mgrPrefs.candidateTextFontName, size: textSize)
|
name: mgrPrefs.candidateTextFontName, size: textSize
|
||||||
|
)
|
||||||
|
|
||||||
let candidateKeys = mgrPrefs.candidateKeys
|
let candidateKeys = mgrPrefs.candidateKeys
|
||||||
let keyLabels =
|
let keyLabels =
|
||||||
|
@ -468,42 +476,47 @@ extension ctlInputMethod {
|
||||||
ctlCandidateCurrent?.visible = true
|
ctlCandidateCurrent?.visible = true
|
||||||
|
|
||||||
var lineHeightRect = NSRect(x: 0.0, y: 0.0, width: 16.0, height: 16.0)
|
var lineHeightRect = NSRect(x: 0.0, y: 0.0, width: 16.0, height: 16.0)
|
||||||
var cursor: Int = 0
|
var cursor = 0
|
||||||
|
|
||||||
if let state = state as? InputState.ChoosingCandidate {
|
if let state = state as? InputState.ChoosingCandidate {
|
||||||
cursor = Int(state.cursorIndex)
|
cursor = Int(state.cursorIndex)
|
||||||
if cursor == state.composingBuffer.count && cursor != 0 {
|
if cursor == state.composingBuffer.count, cursor != 0 {
|
||||||
cursor -= 1
|
cursor -= 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while lineHeightRect.origin.x == 0 && lineHeightRect.origin.y == 0 && cursor >= 0 {
|
while lineHeightRect.origin.x == 0, lineHeightRect.origin.y == 0, cursor >= 0 {
|
||||||
(client as? IMKTextInput)?.attributes(
|
(client as? IMKTextInput)?.attributes(
|
||||||
forCharacterIndex: cursor, lineHeightRectangle: &lineHeightRect)
|
forCharacterIndex: cursor, lineHeightRectangle: &lineHeightRect
|
||||||
|
)
|
||||||
cursor -= 1
|
cursor -= 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if useVerticalMode {
|
if useVerticalMode {
|
||||||
ctlCandidateCurrent?.set(
|
ctlCandidateCurrent?.set(
|
||||||
windowTopLeftPoint: NSPoint(
|
windowTopLeftPoint: NSPoint(
|
||||||
x: lineHeightRect.origin.x + lineHeightRect.size.width + 4.0, y: lineHeightRect.origin.y - 4.0),
|
x: lineHeightRect.origin.x + lineHeightRect.size.width + 4.0, y: lineHeightRect.origin.y - 4.0
|
||||||
bottomOutOfScreenAdjustmentHeight: lineHeightRect.size.height + 4.0)
|
),
|
||||||
|
bottomOutOfScreenAdjustmentHeight: lineHeightRect.size.height + 4.0
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
ctlCandidateCurrent?.set(
|
ctlCandidateCurrent?.set(
|
||||||
windowTopLeftPoint: NSPoint(x: lineHeightRect.origin.x, y: lineHeightRect.origin.y - 4.0),
|
windowTopLeftPoint: NSPoint(x: lineHeightRect.origin.x, y: lineHeightRect.origin.y - 4.0),
|
||||||
bottomOutOfScreenAdjustmentHeight: lineHeightRect.size.height + 4.0)
|
bottomOutOfScreenAdjustmentHeight: lineHeightRect.size.height + 4.0
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func show(tooltip: String, composingBuffer: String, cursorIndex: UInt, client: Any!) {
|
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 lineHeightRect = NSRect(x: 0.0, y: 0.0, width: 16.0, height: 16.0)
|
||||||
var cursor: Int = Int(cursorIndex)
|
var cursor = Int(cursorIndex)
|
||||||
if cursor == composingBuffer.count && cursor != 0 {
|
if cursor == composingBuffer.count, cursor != 0 {
|
||||||
cursor -= 1
|
cursor -= 1
|
||||||
}
|
}
|
||||||
while lineHeightRect.origin.x == 0 && lineHeightRect.origin.y == 0 && cursor >= 0 {
|
while lineHeightRect.origin.x == 0, lineHeightRect.origin.y == 0, cursor >= 0 {
|
||||||
(client as? IMKTextInput)?.attributes(
|
(client as? IMKTextInput)?.attributes(
|
||||||
forCharacterIndex: cursor, lineHeightRectangle: &lineHeightRect)
|
forCharacterIndex: cursor, lineHeightRectangle: &lineHeightRect
|
||||||
|
)
|
||||||
cursor -= 1
|
cursor -= 1
|
||||||
}
|
}
|
||||||
ctlInputMethod.tooltipController.show(tooltip: tooltip, at: lineHeightRect.origin)
|
ctlInputMethod.tooltipController.show(tooltip: tooltip, at: lineHeightRect.origin)
|
||||||
|
@ -517,16 +530,16 @@ extension ctlInputMethod {
|
||||||
// MARK: -
|
// MARK: -
|
||||||
|
|
||||||
extension ctlInputMethod: KeyHandlerDelegate {
|
extension ctlInputMethod: KeyHandlerDelegate {
|
||||||
func ctlCandidate(for keyHandler: KeyHandler) -> Any {
|
func ctlCandidate(for _: KeyHandler) -> Any {
|
||||||
ctlCandidateCurrent ?? .vertical
|
ctlCandidateCurrent ?? .vertical
|
||||||
}
|
}
|
||||||
|
|
||||||
func keyHandler(
|
func keyHandler(
|
||||||
_ keyHandler: KeyHandler, didSelectCandidateAt index: Int,
|
_: KeyHandler, didSelectCandidateAt index: Int,
|
||||||
ctlCandidate controller: Any
|
ctlCandidate controller: Any
|
||||||
) {
|
) {
|
||||||
if let controller = controller as? ctlCandidate {
|
if let controller = controller as? ctlCandidate {
|
||||||
self.ctlCandidate(controller, didSelectCandidateAtIndex: UInt(index))
|
ctlCandidate(controller, didSelectCandidateAtIndex: UInt(index))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -545,11 +558,13 @@ extension ctlInputMethod: KeyHandlerDelegate {
|
||||||
if !mgrLangModel.writeUserPhrase(
|
if !mgrLangModel.writeUserPhrase(
|
||||||
state.userPhrase, inputMode: keyHandler.inputMode,
|
state.userPhrase, inputMode: keyHandler.inputMode,
|
||||||
areWeDuplicating: state.chkIfUserPhraseExists,
|
areWeDuplicating: state.chkIfUserPhraseExists,
|
||||||
areWeDeleting: ctlInputMethod.areWeDeleting)
|
areWeDeleting: ctlInputMethod.areWeDeleting
|
||||||
|
)
|
||||||
|| !mgrLangModel.writeUserPhrase(
|
|| !mgrLangModel.writeUserPhrase(
|
||||||
state.userPhraseConverted, inputMode: refInputModeReversed,
|
state.userPhraseConverted, inputMode: refInputModeReversed,
|
||||||
areWeDuplicating: false,
|
areWeDuplicating: false,
|
||||||
areWeDeleting: ctlInputMethod.areWeDeleting)
|
areWeDeleting: ctlInputMethod.areWeDeleting
|
||||||
|
)
|
||||||
{
|
{
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -560,7 +575,7 @@ extension ctlInputMethod: KeyHandlerDelegate {
|
||||||
// MARK: -
|
// MARK: -
|
||||||
|
|
||||||
extension ctlInputMethod: ctlCandidateDelegate {
|
extension ctlInputMethod: ctlCandidateDelegate {
|
||||||
func candidateCountForController(_ controller: ctlCandidate) -> UInt {
|
func candidateCountForController(_: ctlCandidate) -> UInt {
|
||||||
if let state = state as? InputState.ChoosingCandidate {
|
if let state = state as? InputState.ChoosingCandidate {
|
||||||
return UInt(state.candidates.count)
|
return UInt(state.candidates.count)
|
||||||
} else if let state = state as? InputState.AssociatedPhrases {
|
} else if let state = state as? InputState.AssociatedPhrases {
|
||||||
|
@ -569,7 +584,7 @@ extension ctlInputMethod: ctlCandidateDelegate {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func ctlCandidate(_ controller: ctlCandidate, candidateAtIndex index: UInt)
|
func ctlCandidate(_: ctlCandidate, candidateAtIndex index: UInt)
|
||||||
-> String
|
-> String
|
||||||
{
|
{
|
||||||
if let state = state as? InputState.ChoosingCandidate {
|
if let state = state as? InputState.ChoosingCandidate {
|
||||||
|
@ -580,19 +595,20 @@ extension ctlInputMethod: ctlCandidateDelegate {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func ctlCandidate(_ controller: ctlCandidate, didSelectCandidateAtIndex index: UInt) {
|
func ctlCandidate(_: ctlCandidate, didSelectCandidateAtIndex index: UInt) {
|
||||||
let client = currentClient
|
let client = currentClient
|
||||||
|
|
||||||
if let state = state as? InputState.SymbolTable,
|
if let state = state as? InputState.SymbolTable,
|
||||||
let node = state.node.children?[Int(index)]
|
let node = state.node.children?[Int(index)]
|
||||||
{
|
{
|
||||||
if let children = node.children, !children.isEmpty {
|
if let children = node.children, !children.isEmpty {
|
||||||
self.handle(
|
handle(
|
||||||
state: .SymbolTable(node: node, useVerticalMode: state.useVerticalMode),
|
state: .SymbolTable(node: node, useVerticalMode: state.useVerticalMode),
|
||||||
client: currentClient)
|
client: currentClient
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
self.handle(state: .Committing(poppedText: node.title), client: client)
|
handle(state: .Committing(poppedText: node.title), client: client)
|
||||||
self.handle(state: .Empty(), client: client)
|
handle(state: .Empty(), client: client)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -609,10 +625,11 @@ extension ctlInputMethod: ctlCandidateDelegate {
|
||||||
handle(state: .Committing(poppedText: composingBuffer), client: client)
|
handle(state: .Committing(poppedText: composingBuffer), client: client)
|
||||||
if mgrPrefs.associatedPhrasesEnabled,
|
if mgrPrefs.associatedPhrasesEnabled,
|
||||||
let associatePhrases = keyHandler.buildAssociatePhraseState(
|
let associatePhrases = keyHandler.buildAssociatePhraseState(
|
||||||
withKey: composingBuffer, useVerticalMode: state.useVerticalMode)
|
withKey: composingBuffer, useVerticalMode: state.useVerticalMode
|
||||||
|
)
|
||||||
as? InputState.AssociatedPhrases
|
as? InputState.AssociatedPhrases
|
||||||
{
|
{
|
||||||
self.handle(state: associatePhrases, client: client)
|
handle(state: associatePhrases, client: client)
|
||||||
} else {
|
} else {
|
||||||
handle(state: .Empty(), client: client)
|
handle(state: .Empty(), client: client)
|
||||||
}
|
}
|
||||||
|
@ -627,10 +644,11 @@ extension ctlInputMethod: ctlCandidateDelegate {
|
||||||
handle(state: .Committing(poppedText: selectedValue), client: currentClient)
|
handle(state: .Committing(poppedText: selectedValue), client: currentClient)
|
||||||
if mgrPrefs.associatedPhrasesEnabled,
|
if mgrPrefs.associatedPhrasesEnabled,
|
||||||
let associatePhrases = keyHandler.buildAssociatePhraseState(
|
let associatePhrases = keyHandler.buildAssociatePhraseState(
|
||||||
withKey: selectedValue, useVerticalMode: state.useVerticalMode)
|
withKey: selectedValue, useVerticalMode: state.useVerticalMode
|
||||||
|
)
|
||||||
as? InputState.AssociatedPhrases
|
as? InputState.AssociatedPhrases
|
||||||
{
|
{
|
||||||
self.handle(state: associatePhrases, client: client)
|
handle(state: associatePhrases, client: client)
|
||||||
} else {
|
} else {
|
||||||
handle(state: .Empty(), client: client)
|
handle(state: .Empty(), client: client)
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@ extension Bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - IME Menu Manager
|
// MARK: - IME Menu Manager
|
||||||
|
|
||||||
// 因為選單部分的內容又臭又長,所以就單獨拉到一個檔案內管理了。
|
// 因為選單部分的內容又臭又長,所以就單獨拉到一個檔案內管理了。
|
||||||
|
|
||||||
extension ctlInputMethod {
|
extension ctlInputMethod {
|
||||||
|
@ -43,51 +44,59 @@ extension ctlInputMethod {
|
||||||
|
|
||||||
let useSCPCTypingModeItem = menu.addItem(
|
let useSCPCTypingModeItem = menu.addItem(
|
||||||
withTitle: NSLocalizedString("Per-Char Select Mode", comment: ""),
|
withTitle: NSLocalizedString("Per-Char Select Mode", comment: ""),
|
||||||
action: #selector(toggleSCPCTypingMode(_:)), keyEquivalent: "P")
|
action: #selector(toggleSCPCTypingMode(_:)), keyEquivalent: "P"
|
||||||
|
)
|
||||||
useSCPCTypingModeItem.keyEquivalentModifierMask = [.command, .control]
|
useSCPCTypingModeItem.keyEquivalentModifierMask = [.command, .control]
|
||||||
useSCPCTypingModeItem.state = mgrPrefs.useSCPCTypingMode.state
|
useSCPCTypingModeItem.state = mgrPrefs.useSCPCTypingMode.state
|
||||||
|
|
||||||
let userAssociatedPhrasesItem = menu.addItem(
|
let userAssociatedPhrasesItem = menu.addItem(
|
||||||
withTitle: NSLocalizedString("Per-Char Associated Phrases", comment: ""),
|
withTitle: NSLocalizedString("Per-Char Associated Phrases", comment: ""),
|
||||||
action: #selector(toggleAssociatedPhrasesEnabled(_:)), keyEquivalent: "O")
|
action: #selector(toggleAssociatedPhrasesEnabled(_:)), keyEquivalent: "O"
|
||||||
|
)
|
||||||
userAssociatedPhrasesItem.keyEquivalentModifierMask = [.command, .control]
|
userAssociatedPhrasesItem.keyEquivalentModifierMask = [.command, .control]
|
||||||
userAssociatedPhrasesItem.state = mgrPrefs.associatedPhrasesEnabled.state
|
userAssociatedPhrasesItem.state = mgrPrefs.associatedPhrasesEnabled.state
|
||||||
|
|
||||||
let useCNS11643SupportItem = menu.addItem(
|
let useCNS11643SupportItem = menu.addItem(
|
||||||
withTitle: NSLocalizedString("CNS11643 Mode", comment: ""),
|
withTitle: NSLocalizedString("CNS11643 Mode", comment: ""),
|
||||||
action: #selector(toggleCNS11643Enabled(_:)), keyEquivalent: "L")
|
action: #selector(toggleCNS11643Enabled(_:)), keyEquivalent: "L"
|
||||||
|
)
|
||||||
useCNS11643SupportItem.keyEquivalentModifierMask = [.command, .control]
|
useCNS11643SupportItem.keyEquivalentModifierMask = [.command, .control]
|
||||||
useCNS11643SupportItem.state = mgrPrefs.cns11643Enabled.state
|
useCNS11643SupportItem.state = mgrPrefs.cns11643Enabled.state
|
||||||
|
|
||||||
if IME.getInputMode() == InputMode.imeModeCHT {
|
if IME.getInputMode() == InputMode.imeModeCHT {
|
||||||
let chineseConversionItem = menu.addItem(
|
let chineseConversionItem = menu.addItem(
|
||||||
withTitle: NSLocalizedString("Force KangXi Writing", comment: ""),
|
withTitle: NSLocalizedString("Force KangXi Writing", comment: ""),
|
||||||
action: #selector(toggleChineseConverter(_:)), keyEquivalent: "K")
|
action: #selector(toggleChineseConverter(_:)), keyEquivalent: "K"
|
||||||
|
)
|
||||||
chineseConversionItem.keyEquivalentModifierMask = [.command, .control]
|
chineseConversionItem.keyEquivalentModifierMask = [.command, .control]
|
||||||
chineseConversionItem.state = mgrPrefs.chineseConversionEnabled.state
|
chineseConversionItem.state = mgrPrefs.chineseConversionEnabled.state
|
||||||
|
|
||||||
let shiftJISConversionItem = menu.addItem(
|
let shiftJISConversionItem = menu.addItem(
|
||||||
withTitle: NSLocalizedString("JIS Shinjitai Output", comment: ""),
|
withTitle: NSLocalizedString("JIS Shinjitai Output", comment: ""),
|
||||||
action: #selector(toggleShiftJISShinjitaiOutput(_:)), keyEquivalent: "J")
|
action: #selector(toggleShiftJISShinjitaiOutput(_:)), keyEquivalent: "J"
|
||||||
|
)
|
||||||
shiftJISConversionItem.keyEquivalentModifierMask = [.command, .control]
|
shiftJISConversionItem.keyEquivalentModifierMask = [.command, .control]
|
||||||
shiftJISConversionItem.state = mgrPrefs.shiftJISShinjitaiOutputEnabled.state
|
shiftJISConversionItem.state = mgrPrefs.shiftJISShinjitaiOutputEnabled.state
|
||||||
}
|
}
|
||||||
|
|
||||||
let halfWidthPunctuationItem = menu.addItem(
|
let halfWidthPunctuationItem = menu.addItem(
|
||||||
withTitle: NSLocalizedString("Half-Width Punctuation Mode", comment: ""),
|
withTitle: NSLocalizedString("Half-Width Punctuation Mode", comment: ""),
|
||||||
action: #selector(toggleHalfWidthPunctuation(_:)), keyEquivalent: "H")
|
action: #selector(toggleHalfWidthPunctuation(_:)), keyEquivalent: "H"
|
||||||
|
)
|
||||||
halfWidthPunctuationItem.keyEquivalentModifierMask = [.command, .control]
|
halfWidthPunctuationItem.keyEquivalentModifierMask = [.command, .control]
|
||||||
halfWidthPunctuationItem.state = mgrPrefs.halfWidthPunctuationEnabled.state
|
halfWidthPunctuationItem.state = mgrPrefs.halfWidthPunctuationEnabled.state
|
||||||
|
|
||||||
if optionKeyPressed {
|
if optionKeyPressed {
|
||||||
let phaseReplacementItem = menu.addItem(
|
let phaseReplacementItem = menu.addItem(
|
||||||
withTitle: NSLocalizedString("Use Phrase Replacement", comment: ""),
|
withTitle: NSLocalizedString("Use Phrase Replacement", comment: ""),
|
||||||
action: #selector(togglePhraseReplacement(_:)), keyEquivalent: "")
|
action: #selector(togglePhraseReplacement(_:)), keyEquivalent: ""
|
||||||
|
)
|
||||||
phaseReplacementItem.state = mgrPrefs.phraseReplacementEnabled.state
|
phaseReplacementItem.state = mgrPrefs.phraseReplacementEnabled.state
|
||||||
|
|
||||||
let toggleSymbolInputItem = menu.addItem(
|
let toggleSymbolInputItem = menu.addItem(
|
||||||
withTitle: NSLocalizedString("Symbol & Emoji Input", comment: ""),
|
withTitle: NSLocalizedString("Symbol & Emoji Input", comment: ""),
|
||||||
action: #selector(toggleSymbolEnabled(_:)), keyEquivalent: "")
|
action: #selector(toggleSymbolEnabled(_:)), keyEquivalent: ""
|
||||||
|
)
|
||||||
toggleSymbolInputItem.state = mgrPrefs.symbolInputEnabled.state
|
toggleSymbolInputItem.state = mgrPrefs.symbolInputEnabled.state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,30 +104,37 @@ extension ctlInputMethod {
|
||||||
|
|
||||||
menu.addItem(
|
menu.addItem(
|
||||||
withTitle: NSLocalizedString("Open User Data Folder", comment: ""),
|
withTitle: NSLocalizedString("Open User Data Folder", comment: ""),
|
||||||
action: #selector(openUserDataFolder(_:)), keyEquivalent: "")
|
action: #selector(openUserDataFolder(_:)), keyEquivalent: ""
|
||||||
|
)
|
||||||
menu.addItem(
|
menu.addItem(
|
||||||
withTitle: NSLocalizedString("Edit User Phrases…", comment: ""),
|
withTitle: NSLocalizedString("Edit User Phrases…", comment: ""),
|
||||||
action: #selector(openUserPhrases(_:)), keyEquivalent: "")
|
action: #selector(openUserPhrases(_:)), keyEquivalent: ""
|
||||||
|
)
|
||||||
menu.addItem(
|
menu.addItem(
|
||||||
withTitle: NSLocalizedString("Edit Excluded Phrases…", comment: ""),
|
withTitle: NSLocalizedString("Edit Excluded Phrases…", comment: ""),
|
||||||
action: #selector(openExcludedPhrases(_:)), keyEquivalent: "")
|
action: #selector(openExcludedPhrases(_:)), keyEquivalent: ""
|
||||||
|
)
|
||||||
|
|
||||||
if optionKeyPressed {
|
if optionKeyPressed {
|
||||||
menu.addItem(
|
menu.addItem(
|
||||||
withTitle: NSLocalizedString("Edit Phrase Replacement Table…", comment: ""),
|
withTitle: NSLocalizedString("Edit Phrase Replacement Table…", comment: ""),
|
||||||
action: #selector(openPhraseReplacement(_:)), keyEquivalent: "")
|
action: #selector(openPhraseReplacement(_:)), keyEquivalent: ""
|
||||||
|
)
|
||||||
menu.addItem(
|
menu.addItem(
|
||||||
withTitle: NSLocalizedString("Edit Associated Phrases…", comment: ""),
|
withTitle: NSLocalizedString("Edit Associated Phrases…", comment: ""),
|
||||||
action: #selector(openAssociatedPhrases(_:)), keyEquivalent: "")
|
action: #selector(openAssociatedPhrases(_:)), keyEquivalent: ""
|
||||||
|
)
|
||||||
menu.addItem(
|
menu.addItem(
|
||||||
withTitle: NSLocalizedString("Edit User Symbol & Emoji Data…", comment: ""),
|
withTitle: NSLocalizedString("Edit User Symbol & Emoji Data…", comment: ""),
|
||||||
action: #selector(openUserSymbols(_:)), keyEquivalent: "")
|
action: #selector(openUserSymbols(_:)), keyEquivalent: ""
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if optionKeyPressed || !mgrPrefs.shouldAutoReloadUserDataFiles {
|
if optionKeyPressed || !mgrPrefs.shouldAutoReloadUserDataFiles {
|
||||||
menu.addItem(
|
menu.addItem(
|
||||||
withTitle: NSLocalizedString("Reload User Phrases", comment: ""),
|
withTitle: NSLocalizedString("Reload User Phrases", comment: ""),
|
||||||
action: #selector(reloadUserPhrases(_:)), keyEquivalent: "")
|
action: #selector(reloadUserPhrases(_:)), keyEquivalent: ""
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
menu.addItem(NSMenuItem.separator()) // ---------------------
|
menu.addItem(NSMenuItem.separator()) // ---------------------
|
||||||
|
@ -126,25 +142,31 @@ extension ctlInputMethod {
|
||||||
if optionKeyPressed {
|
if optionKeyPressed {
|
||||||
menu.addItem(
|
menu.addItem(
|
||||||
withTitle: NSLocalizedString("vChewing Preferences…", comment: ""),
|
withTitle: NSLocalizedString("vChewing Preferences…", comment: ""),
|
||||||
action: #selector(showLegacyPreferences(_:)), keyEquivalent: "")
|
action: #selector(showLegacyPreferences(_:)), keyEquivalent: ""
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
menu.addItem(
|
menu.addItem(
|
||||||
withTitle: NSLocalizedString("vChewing Preferences…", comment: ""),
|
withTitle: NSLocalizedString("vChewing Preferences…", comment: ""),
|
||||||
action: #selector(showPreferences(_:)), keyEquivalent: "")
|
action: #selector(showPreferences(_:)), keyEquivalent: ""
|
||||||
|
)
|
||||||
menu.addItem(
|
menu.addItem(
|
||||||
withTitle: NSLocalizedString("Check for Updates…", comment: ""),
|
withTitle: NSLocalizedString("Check for Updates…", comment: ""),
|
||||||
action: #selector(checkForUpdate(_:)), keyEquivalent: "")
|
action: #selector(checkForUpdate(_:)), keyEquivalent: ""
|
||||||
|
)
|
||||||
}
|
}
|
||||||
menu.addItem(
|
menu.addItem(
|
||||||
withTitle: NSLocalizedString("Reboot vChewing…", comment: ""),
|
withTitle: NSLocalizedString("Reboot vChewing…", comment: ""),
|
||||||
action: #selector(selfTerminate(_:)), keyEquivalent: "")
|
action: #selector(selfTerminate(_:)), keyEquivalent: ""
|
||||||
|
)
|
||||||
menu.addItem(
|
menu.addItem(
|
||||||
withTitle: NSLocalizedString("About vChewing…", comment: ""),
|
withTitle: NSLocalizedString("About vChewing…", comment: ""),
|
||||||
action: #selector(showAbout(_:)), keyEquivalent: "")
|
action: #selector(showAbout(_:)), keyEquivalent: ""
|
||||||
|
)
|
||||||
if optionKeyPressed {
|
if optionKeyPressed {
|
||||||
menu.addItem(
|
menu.addItem(
|
||||||
withTitle: NSLocalizedString("Uninstall vChewing…", comment: ""),
|
withTitle: NSLocalizedString("Uninstall vChewing…", comment: ""),
|
||||||
action: #selector(selfUninstall(_:)), keyEquivalent: "")
|
action: #selector(selfUninstall(_:)), keyEquivalent: ""
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NSMenu 會阻止任何 modified key 相關的訊號傳回輸入法,所以咱們在此重設鍵盤佈局
|
// NSMenu 會阻止任何 modified key 相關的訊號傳回輸入法,所以咱們在此重設鍵盤佈局
|
||||||
|
@ -155,7 +177,7 @@ extension ctlInputMethod {
|
||||||
|
|
||||||
// MARK: - IME Menu Items
|
// MARK: - IME Menu Items
|
||||||
|
|
||||||
@objc override func showPreferences(_ sender: Any?) {
|
@objc override func showPreferences(_: Any?) {
|
||||||
if #available(macOS 11.0, *) {
|
if #available(macOS 11.0, *) {
|
||||||
NSApp.setActivationPolicy(.accessory)
|
NSApp.setActivationPolicy(.accessory)
|
||||||
ctlPrefUI.shared.controller.show(preferencePane: Preferences.PaneIdentifier(rawValue: "General"))
|
ctlPrefUI.shared.controller.show(preferencePane: Preferences.PaneIdentifier(rawValue: "General"))
|
||||||
|
@ -165,7 +187,7 @@ extension ctlInputMethod {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func showLegacyPreferences(_ sender: Any?) {
|
@objc func showLegacyPreferences(_: Any?) {
|
||||||
showPrefWindowTraditional()
|
showPrefWindowTraditional()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,135 +196,144 @@ extension ctlInputMethod {
|
||||||
NSApp.activate(ignoringOtherApps: true)
|
NSApp.activate(ignoringOtherApps: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func toggleSCPCTypingMode(_ sender: Any?) {
|
@objc func toggleSCPCTypingMode(_: Any?) {
|
||||||
NotifierController.notify(
|
NotifierController.notify(
|
||||||
message: String(
|
message: String(
|
||||||
format: "%@%@%@", NSLocalizedString("Per-Char Select Mode", comment: ""), "\n",
|
format: "%@%@%@", NSLocalizedString("Per-Char Select Mode", comment: ""), "\n",
|
||||||
mgrPrefs.toggleSCPCTypingModeEnabled()
|
mgrPrefs.toggleSCPCTypingModeEnabled()
|
||||||
? NSLocalizedString("NotificationSwitchON", comment: "")
|
? NSLocalizedString("NotificationSwitchON", comment: "")
|
||||||
: NSLocalizedString("NotificationSwitchOFF", comment: "")))
|
: NSLocalizedString("NotificationSwitchOFF", comment: "")
|
||||||
|
))
|
||||||
resetKeyHandler()
|
resetKeyHandler()
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func toggleChineseConverter(_ sender: Any?) {
|
@objc func toggleChineseConverter(_: Any?) {
|
||||||
NotifierController.notify(
|
NotifierController.notify(
|
||||||
message: String(
|
message: String(
|
||||||
format: "%@%@%@", NSLocalizedString("Force KangXi Writing", comment: ""), "\n",
|
format: "%@%@%@", NSLocalizedString("Force KangXi Writing", comment: ""), "\n",
|
||||||
mgrPrefs.toggleChineseConversionEnabled()
|
mgrPrefs.toggleChineseConversionEnabled()
|
||||||
? NSLocalizedString("NotificationSwitchON", comment: "")
|
? NSLocalizedString("NotificationSwitchON", comment: "")
|
||||||
: NSLocalizedString("NotificationSwitchOFF", comment: "")))
|
: NSLocalizedString("NotificationSwitchOFF", comment: "")
|
||||||
|
))
|
||||||
resetKeyHandler()
|
resetKeyHandler()
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func toggleShiftJISShinjitaiOutput(_ sender: Any?) {
|
@objc func toggleShiftJISShinjitaiOutput(_: Any?) {
|
||||||
NotifierController.notify(
|
NotifierController.notify(
|
||||||
message: String(
|
message: String(
|
||||||
format: "%@%@%@", NSLocalizedString("JIS Shinjitai Output", comment: ""), "\n",
|
format: "%@%@%@", NSLocalizedString("JIS Shinjitai Output", comment: ""), "\n",
|
||||||
mgrPrefs.toggleShiftJISShinjitaiOutputEnabled()
|
mgrPrefs.toggleShiftJISShinjitaiOutputEnabled()
|
||||||
? NSLocalizedString("NotificationSwitchON", comment: "")
|
? NSLocalizedString("NotificationSwitchON", comment: "")
|
||||||
: NSLocalizedString("NotificationSwitchOFF", comment: "")))
|
: NSLocalizedString("NotificationSwitchOFF", comment: "")
|
||||||
|
))
|
||||||
resetKeyHandler()
|
resetKeyHandler()
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func toggleHalfWidthPunctuation(_ sender: Any?) {
|
@objc func toggleHalfWidthPunctuation(_: Any?) {
|
||||||
NotifierController.notify(
|
NotifierController.notify(
|
||||||
message: String(
|
message: String(
|
||||||
format: "%@%@%@", NSLocalizedString("Half-Width Punctuation Mode", comment: ""),
|
format: "%@%@%@", NSLocalizedString("Half-Width Punctuation Mode", comment: ""),
|
||||||
"\n",
|
"\n",
|
||||||
mgrPrefs.toggleHalfWidthPunctuationEnabled()
|
mgrPrefs.toggleHalfWidthPunctuationEnabled()
|
||||||
? NSLocalizedString("NotificationSwitchON", comment: "")
|
? NSLocalizedString("NotificationSwitchON", comment: "")
|
||||||
: NSLocalizedString("NotificationSwitchOFF", comment: "")))
|
: NSLocalizedString("NotificationSwitchOFF", comment: "")
|
||||||
|
))
|
||||||
resetKeyHandler()
|
resetKeyHandler()
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func toggleCNS11643Enabled(_ sender: Any?) {
|
@objc func toggleCNS11643Enabled(_: Any?) {
|
||||||
NotifierController.notify(
|
NotifierController.notify(
|
||||||
message: String(
|
message: String(
|
||||||
format: "%@%@%@", NSLocalizedString("CNS11643 Mode", comment: ""), "\n",
|
format: "%@%@%@", NSLocalizedString("CNS11643 Mode", comment: ""), "\n",
|
||||||
mgrPrefs.toggleCNS11643Enabled()
|
mgrPrefs.toggleCNS11643Enabled()
|
||||||
? NSLocalizedString("NotificationSwitchON", comment: "")
|
? NSLocalizedString("NotificationSwitchON", comment: "")
|
||||||
: NSLocalizedString("NotificationSwitchOFF", comment: "")))
|
: NSLocalizedString("NotificationSwitchOFF", comment: "")
|
||||||
|
))
|
||||||
resetKeyHandler()
|
resetKeyHandler()
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func toggleSymbolEnabled(_ sender: Any?) {
|
@objc func toggleSymbolEnabled(_: Any?) {
|
||||||
NotifierController.notify(
|
NotifierController.notify(
|
||||||
message: String(
|
message: String(
|
||||||
format: "%@%@%@", NSLocalizedString("Symbol & Emoji Input", comment: ""), "\n",
|
format: "%@%@%@", NSLocalizedString("Symbol & Emoji Input", comment: ""), "\n",
|
||||||
mgrPrefs.toggleSymbolInputEnabled()
|
mgrPrefs.toggleSymbolInputEnabled()
|
||||||
? NSLocalizedString("NotificationSwitchON", comment: "")
|
? NSLocalizedString("NotificationSwitchON", comment: "")
|
||||||
: NSLocalizedString("NotificationSwitchOFF", comment: "")))
|
: NSLocalizedString("NotificationSwitchOFF", comment: "")
|
||||||
|
))
|
||||||
resetKeyHandler()
|
resetKeyHandler()
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func toggleAssociatedPhrasesEnabled(_ sender: Any?) {
|
@objc func toggleAssociatedPhrasesEnabled(_: Any?) {
|
||||||
NotifierController.notify(
|
NotifierController.notify(
|
||||||
message: String(
|
message: String(
|
||||||
format: "%@%@%@", NSLocalizedString("Per-Char Associated Phrases", comment: ""),
|
format: "%@%@%@", NSLocalizedString("Per-Char Associated Phrases", comment: ""),
|
||||||
"\n",
|
"\n",
|
||||||
mgrPrefs.toggleAssociatedPhrasesEnabled()
|
mgrPrefs.toggleAssociatedPhrasesEnabled()
|
||||||
? NSLocalizedString("NotificationSwitchON", comment: "")
|
? NSLocalizedString("NotificationSwitchON", comment: "")
|
||||||
: NSLocalizedString("NotificationSwitchOFF", comment: "")))
|
: NSLocalizedString("NotificationSwitchOFF", comment: "")
|
||||||
|
))
|
||||||
resetKeyHandler()
|
resetKeyHandler()
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func togglePhraseReplacement(_ sender: Any?) {
|
@objc func togglePhraseReplacement(_: Any?) {
|
||||||
NotifierController.notify(
|
NotifierController.notify(
|
||||||
message: String(
|
message: String(
|
||||||
format: "%@%@%@", NSLocalizedString("Use Phrase Replacement", comment: ""), "\n",
|
format: "%@%@%@", NSLocalizedString("Use Phrase Replacement", comment: ""), "\n",
|
||||||
mgrPrefs.togglePhraseReplacementEnabled()
|
mgrPrefs.togglePhraseReplacementEnabled()
|
||||||
? NSLocalizedString("NotificationSwitchON", comment: "")
|
? NSLocalizedString("NotificationSwitchON", comment: "")
|
||||||
: NSLocalizedString("NotificationSwitchOFF", comment: "")))
|
: NSLocalizedString("NotificationSwitchOFF", comment: "")
|
||||||
|
))
|
||||||
resetKeyHandler()
|
resetKeyHandler()
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func selfUninstall(_ sender: Any?) {
|
@objc func selfUninstall(_: Any?) {
|
||||||
(NSApp.delegate as? AppDelegate)?.selfUninstall()
|
(NSApp.delegate as? AppDelegate)?.selfUninstall()
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func selfTerminate(_ sender: Any?) {
|
@objc func selfTerminate(_: Any?) {
|
||||||
NSApp.activate(ignoringOtherApps: true)
|
NSApp.activate(ignoringOtherApps: true)
|
||||||
NSApp.terminate(nil)
|
NSApp.terminate(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func checkForUpdate(_ sender: Any?) {
|
@objc func checkForUpdate(_: Any?) {
|
||||||
(NSApp.delegate as? AppDelegate)?.checkForUpdate(forced: true)
|
(NSApp.delegate as? AppDelegate)?.checkForUpdate(forced: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func openUserPhrases(_ sender: Any?) {
|
@objc func openUserPhrases(_: Any?) {
|
||||||
IME.openPhraseFile(userFileAt: mgrLangModel.userPhrasesDataPath(IME.getInputMode()))
|
IME.openPhraseFile(userFileAt: mgrLangModel.userPhrasesDataPath(IME.getInputMode()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func openUserDataFolder(_ sender: Any?) {
|
@objc func openUserDataFolder(_: Any?) {
|
||||||
if !mgrLangModel.checkIfUserDataFolderExists() {
|
if !mgrLangModel.checkIfUserDataFolderExists() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
NSWorkspace.shared.openFile(
|
NSWorkspace.shared.openFile(
|
||||||
mgrLangModel.dataFolderPath(isDefaultFolder: false), withApplication: "Finder")
|
mgrLangModel.dataFolderPath(isDefaultFolder: false), withApplication: "Finder"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func openExcludedPhrases(_ sender: Any?) {
|
@objc func openExcludedPhrases(_: Any?) {
|
||||||
IME.openPhraseFile(userFileAt: mgrLangModel.excludedPhrasesDataPath(IME.getInputMode()))
|
IME.openPhraseFile(userFileAt: mgrLangModel.excludedPhrasesDataPath(IME.getInputMode()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func openUserSymbols(_ sender: Any?) {
|
@objc func openUserSymbols(_: Any?) {
|
||||||
IME.openPhraseFile(userFileAt: mgrLangModel.userSymbolDataPath(IME.getInputMode()))
|
IME.openPhraseFile(userFileAt: mgrLangModel.userSymbolDataPath(IME.getInputMode()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func openPhraseReplacement(_ sender: Any?) {
|
@objc func openPhraseReplacement(_: Any?) {
|
||||||
IME.openPhraseFile(userFileAt: mgrLangModel.phraseReplacementDataPath(IME.getInputMode()))
|
IME.openPhraseFile(userFileAt: mgrLangModel.phraseReplacementDataPath(IME.getInputMode()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func openAssociatedPhrases(_ sender: Any?) {
|
@objc func openAssociatedPhrases(_: Any?) {
|
||||||
IME.openPhraseFile(userFileAt: mgrLangModel.userAssociatedPhrasesDataPath(IME.getInputMode()))
|
IME.openPhraseFile(userFileAt: mgrLangModel.userAssociatedPhrasesDataPath(IME.getInputMode()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func reloadUserPhrases(_ sender: Any?) {
|
@objc func reloadUserPhrases(_: Any?) {
|
||||||
mgrLangModel.loadUserPhrases()
|
mgrLangModel.loadUserPhrases()
|
||||||
mgrLangModel.loadUserPhraseReplacement()
|
mgrLangModel.loadUserPhraseReplacement()
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func showAbout(_ sender: Any?) {
|
@objc func showAbout(_: Any?) {
|
||||||
(NSApp.delegate as? AppDelegate)?.showAbout()
|
(NSApp.delegate as? AppDelegate)?.showAbout()
|
||||||
NSApp.activate(ignoringOtherApps: true)
|
NSApp.activate(ignoringOtherApps: true)
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,7 +83,7 @@ private let kDefaultKeys = "123456789"
|
||||||
@objc extension UserDefaults {
|
@objc extension UserDefaults {
|
||||||
func setDefault(_ value: Any?, forKey defaultName: String) {
|
func setDefault(_ value: Any?, forKey defaultName: String) {
|
||||||
if object(forKey: defaultName) == nil {
|
if object(forKey: defaultName) == nil {
|
||||||
self.set(value, forKey: defaultName)
|
set(value, forKey: defaultName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,9 +110,7 @@ struct UserDefault<Value> {
|
||||||
struct CandidateListTextSize {
|
struct CandidateListTextSize {
|
||||||
let key: String
|
let key: String
|
||||||
let defaultValue: CGFloat = kDefaultCandidateListTextSize
|
let defaultValue: CGFloat = kDefaultCandidateListTextSize
|
||||||
lazy var container: UserDefault = {
|
lazy var container: UserDefault = .init(key: key, defaultValue: defaultValue)
|
||||||
UserDefault(key: key, defaultValue: defaultValue)
|
|
||||||
}()
|
|
||||||
|
|
||||||
var wrappedValue: CGFloat {
|
var wrappedValue: CGFloat {
|
||||||
mutating get {
|
mutating get {
|
||||||
|
@ -140,9 +138,7 @@ struct CandidateListTextSize {
|
||||||
struct ComposingBufferSize {
|
struct ComposingBufferSize {
|
||||||
let key: String
|
let key: String
|
||||||
let defaultValue: Int = kDefaultComposingBufferSize
|
let defaultValue: Int = kDefaultComposingBufferSize
|
||||||
lazy var container: UserDefault = {
|
lazy var container: UserDefault = .init(key: key, defaultValue: defaultValue)
|
||||||
UserDefault(key: key, defaultValue: defaultValue)
|
|
||||||
}()
|
|
||||||
|
|
||||||
var wrappedValue: Int {
|
var wrappedValue: Int {
|
||||||
mutating get {
|
mutating get {
|
||||||
|
@ -201,6 +197,7 @@ struct ComposingBufferSize {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: -
|
// MARK: -
|
||||||
|
|
||||||
public class mgrPrefs: NSObject {
|
public class mgrPrefs: NSObject {
|
||||||
static var allKeys: [String] {
|
static var allKeys: [String] {
|
||||||
[
|
[
|
||||||
|
@ -238,33 +235,42 @@ public class mgrPrefs: NSObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 既然 Preferences Module 的預設屬性不自動寫入 plist,那這邊就先寫入了。
|
// MARK: - 既然 Preferences Module 的預設屬性不自動寫入 plist,那這邊就先寫入了。
|
||||||
|
|
||||||
@objc public static func setMissingDefaults() {
|
@objc public static func setMissingDefaults() {
|
||||||
UserDefaults.standard.setDefault(mgrPrefs.isDebugModeEnabled, forKey: UserDef.kIsDebugModeEnabled)
|
UserDefaults.standard.setDefault(mgrPrefs.isDebugModeEnabled, forKey: UserDef.kIsDebugModeEnabled)
|
||||||
UserDefaults.standard.setDefault(mgrPrefs.mostRecentInputMode, forKey: UserDef.kMostRecentInputMode)
|
UserDefaults.standard.setDefault(mgrPrefs.mostRecentInputMode, forKey: UserDef.kMostRecentInputMode)
|
||||||
UserDefaults.standard.setDefault(mgrPrefs.checkUpdateAutomatically, forKey: UserDef.kCheckUpdateAutomatically)
|
UserDefaults.standard.setDefault(mgrPrefs.checkUpdateAutomatically, forKey: UserDef.kCheckUpdateAutomatically)
|
||||||
UserDefaults.standard.setDefault(
|
UserDefaults.standard.setDefault(
|
||||||
mgrPrefs.showPageButtonsInCandidateWindow, forKey: UserDef.kShowPageButtonsInCandidateWindow)
|
mgrPrefs.showPageButtonsInCandidateWindow, forKey: UserDef.kShowPageButtonsInCandidateWindow
|
||||||
|
)
|
||||||
UserDefaults.standard.setDefault(mgrPrefs.symbolInputEnabled, forKey: UserDef.kSymbolInputEnabled)
|
UserDefaults.standard.setDefault(mgrPrefs.symbolInputEnabled, forKey: UserDef.kSymbolInputEnabled)
|
||||||
UserDefaults.standard.setDefault(mgrPrefs.candidateListTextSize, forKey: UserDef.kCandidateListTextSize)
|
UserDefaults.standard.setDefault(mgrPrefs.candidateListTextSize, forKey: UserDef.kCandidateListTextSize)
|
||||||
UserDefaults.standard.setDefault(mgrPrefs.chooseCandidateUsingSpace, forKey: UserDef.kChooseCandidateUsingSpace)
|
UserDefaults.standard.setDefault(mgrPrefs.chooseCandidateUsingSpace, forKey: UserDef.kChooseCandidateUsingSpace)
|
||||||
UserDefaults.standard.setDefault(
|
UserDefaults.standard.setDefault(
|
||||||
mgrPrefs.shouldAutoReloadUserDataFiles, forKey: UserDef.kShouldAutoReloadUserDataFiles)
|
mgrPrefs.shouldAutoReloadUserDataFiles, forKey: UserDef.kShouldAutoReloadUserDataFiles
|
||||||
|
)
|
||||||
UserDefaults.standard.setDefault(
|
UserDefaults.standard.setDefault(
|
||||||
mgrPrefs.specifyShiftTabKeyBehavior, forKey: UserDef.kSpecifyShiftTabKeyBehavior)
|
mgrPrefs.specifyShiftTabKeyBehavior, forKey: UserDef.kSpecifyShiftTabKeyBehavior
|
||||||
|
)
|
||||||
UserDefaults.standard.setDefault(
|
UserDefaults.standard.setDefault(
|
||||||
mgrPrefs.specifyShiftSpaceKeyBehavior, forKey: UserDef.kSpecifyShiftSpaceKeyBehavior)
|
mgrPrefs.specifyShiftSpaceKeyBehavior, forKey: UserDef.kSpecifyShiftSpaceKeyBehavior
|
||||||
|
)
|
||||||
UserDefaults.standard.setDefault(mgrPrefs.useSCPCTypingMode, forKey: UserDef.kUseSCPCTypingMode)
|
UserDefaults.standard.setDefault(mgrPrefs.useSCPCTypingMode, forKey: UserDef.kUseSCPCTypingMode)
|
||||||
UserDefaults.standard.setDefault(mgrPrefs.associatedPhrasesEnabled, forKey: UserDef.kAssociatedPhrasesEnabled)
|
UserDefaults.standard.setDefault(mgrPrefs.associatedPhrasesEnabled, forKey: UserDef.kAssociatedPhrasesEnabled)
|
||||||
UserDefaults.standard.setDefault(
|
UserDefaults.standard.setDefault(
|
||||||
mgrPrefs.selectPhraseAfterCursorAsCandidate, forKey: UserDef.kSelectPhraseAfterCursorAsCandidate)
|
mgrPrefs.selectPhraseAfterCursorAsCandidate, forKey: UserDef.kSelectPhraseAfterCursorAsCandidate
|
||||||
|
)
|
||||||
UserDefaults.standard.setDefault(
|
UserDefaults.standard.setDefault(
|
||||||
mgrPrefs.moveCursorAfterSelectingCandidate, forKey: UserDef.kMoveCursorAfterSelectingCandidate)
|
mgrPrefs.moveCursorAfterSelectingCandidate, forKey: UserDef.kMoveCursorAfterSelectingCandidate
|
||||||
|
)
|
||||||
UserDefaults.standard.setDefault(
|
UserDefaults.standard.setDefault(
|
||||||
mgrPrefs.useHorizontalCandidateList, forKey: UserDef.kUseHorizontalCandidateList)
|
mgrPrefs.useHorizontalCandidateList, forKey: UserDef.kUseHorizontalCandidateList
|
||||||
|
)
|
||||||
UserDefaults.standard.setDefault(mgrPrefs.cns11643Enabled, forKey: UserDef.kCNS11643Enabled)
|
UserDefaults.standard.setDefault(mgrPrefs.cns11643Enabled, forKey: UserDef.kCNS11643Enabled)
|
||||||
UserDefaults.standard.setDefault(mgrPrefs.chineseConversionEnabled, forKey: UserDef.kChineseConversionEnabled)
|
UserDefaults.standard.setDefault(mgrPrefs.chineseConversionEnabled, forKey: UserDef.kChineseConversionEnabled)
|
||||||
UserDefaults.standard.setDefault(
|
UserDefaults.standard.setDefault(
|
||||||
mgrPrefs.shiftJISShinjitaiOutputEnabled, forKey: UserDef.kShiftJISShinjitaiOutputEnabled)
|
mgrPrefs.shiftJISShinjitaiOutputEnabled, forKey: UserDef.kShiftJISShinjitaiOutputEnabled
|
||||||
|
)
|
||||||
UserDefaults.standard.setDefault(mgrPrefs.phraseReplacementEnabled, forKey: UserDef.kPhraseReplacementEnabled)
|
UserDefaults.standard.setDefault(mgrPrefs.phraseReplacementEnabled, forKey: UserDef.kPhraseReplacementEnabled)
|
||||||
UserDefaults.standard.setDefault(mgrPrefs.shouldNotFartInLieuOfBeep, forKey: UserDef.kShouldNotFartInLieuOfBeep)
|
UserDefaults.standard.setDefault(mgrPrefs.shouldNotFartInLieuOfBeep, forKey: UserDef.kShouldNotFartInLieuOfBeep)
|
||||||
|
|
||||||
|
@ -303,7 +309,8 @@ public class mgrPrefs: NSObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
@UserDefault(
|
@UserDefault(
|
||||||
key: UserDef.kBasicKeyboardLayout, defaultValue: "com.apple.keylayout.ZhuyinBopomofo")
|
key: UserDef.kBasicKeyboardLayout, defaultValue: "com.apple.keylayout.ZhuyinBopomofo"
|
||||||
|
)
|
||||||
@objc static var basicKeyboardLayout: String
|
@objc static var basicKeyboardLayout: String
|
||||||
|
|
||||||
@UserDefault(key: UserDef.kShowPageButtonsInCandidateWindow, defaultValue: true)
|
@UserDefault(key: UserDef.kShowPageButtonsInCandidateWindow, defaultValue: true)
|
||||||
|
@ -377,10 +384,11 @@ public class mgrPrefs: NSObject {
|
||||||
@objc @discardableResult static func toggleChineseConversionEnabled() -> Bool {
|
@objc @discardableResult static func toggleChineseConversionEnabled() -> Bool {
|
||||||
chineseConversionEnabled = !chineseConversionEnabled
|
chineseConversionEnabled = !chineseConversionEnabled
|
||||||
// 康熙轉換與 JIS 轉換不能同時開啟,否則會出現某些奇奇怪怪的情況
|
// 康熙轉換與 JIS 轉換不能同時開啟,否則會出現某些奇奇怪怪的情況
|
||||||
if chineseConversionEnabled && shiftJISShinjitaiOutputEnabled {
|
if chineseConversionEnabled, shiftJISShinjitaiOutputEnabled {
|
||||||
toggleShiftJISShinjitaiOutputEnabled()
|
toggleShiftJISShinjitaiOutputEnabled()
|
||||||
UserDefaults.standard.set(
|
UserDefaults.standard.set(
|
||||||
shiftJISShinjitaiOutputEnabled, forKey: UserDef.kShiftJISShinjitaiOutputEnabled)
|
shiftJISShinjitaiOutputEnabled, forKey: UserDef.kShiftJISShinjitaiOutputEnabled
|
||||||
|
)
|
||||||
}
|
}
|
||||||
UserDefaults.standard.set(chineseConversionEnabled, forKey: UserDef.kChineseConversionEnabled)
|
UserDefaults.standard.set(chineseConversionEnabled, forKey: UserDef.kChineseConversionEnabled)
|
||||||
return chineseConversionEnabled
|
return chineseConversionEnabled
|
||||||
|
@ -392,11 +400,12 @@ public class mgrPrefs: NSObject {
|
||||||
@objc @discardableResult static func toggleShiftJISShinjitaiOutputEnabled() -> Bool {
|
@objc @discardableResult static func toggleShiftJISShinjitaiOutputEnabled() -> Bool {
|
||||||
shiftJISShinjitaiOutputEnabled = !shiftJISShinjitaiOutputEnabled
|
shiftJISShinjitaiOutputEnabled = !shiftJISShinjitaiOutputEnabled
|
||||||
// 康熙轉換與 JIS 轉換不能同時開啟,否則會出現某些奇奇怪怪的情況
|
// 康熙轉換與 JIS 轉換不能同時開啟,否則會出現某些奇奇怪怪的情況
|
||||||
if shiftJISShinjitaiOutputEnabled && chineseConversionEnabled {
|
if shiftJISShinjitaiOutputEnabled, chineseConversionEnabled {
|
||||||
toggleChineseConversionEnabled()
|
toggleChineseConversionEnabled()
|
||||||
}
|
}
|
||||||
UserDefaults.standard.set(
|
UserDefaults.standard.set(
|
||||||
shiftJISShinjitaiOutputEnabled, forKey: UserDef.kShiftJISShinjitaiOutputEnabled)
|
shiftJISShinjitaiOutputEnabled, forKey: UserDef.kShiftJISShinjitaiOutputEnabled
|
||||||
|
)
|
||||||
return shiftJISShinjitaiOutputEnabled
|
return shiftJISShinjitaiOutputEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -418,6 +427,7 @@ public class mgrPrefs: NSObject {
|
||||||
@objc static var specifyShiftSpaceKeyBehavior: Bool
|
@objc static var specifyShiftSpaceKeyBehavior: Bool
|
||||||
|
|
||||||
// MARK: - Optional settings
|
// MARK: - Optional settings
|
||||||
|
|
||||||
@UserDefault(key: UserDef.kCandidateTextFontName, defaultValue: nil)
|
@UserDefault(key: UserDef.kCandidateTextFontName, defaultValue: nil)
|
||||||
@objc static var candidateTextFontName: String?
|
@objc static var candidateTextFontName: String?
|
||||||
|
|
||||||
|
@ -430,6 +440,7 @@ public class mgrPrefs: NSObject {
|
||||||
@objc static var defaultCandidateKeys: String {
|
@objc static var defaultCandidateKeys: String {
|
||||||
kDefaultKeys
|
kDefaultKeys
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc static var suggestedCandidateKeys: [String] {
|
@objc static var suggestedCandidateKeys: [String] {
|
||||||
[kDefaultKeys, "234567890", "QWERTYUIO", "QWERTASDF", "ASDFGHJKL", "ASDFZXCVB"]
|
[kDefaultKeys, "234567890", "QWERTYUIO", "QWERTASDF", "ASDFGHJKL", "ASDFZXCVB"]
|
||||||
}
|
}
|
||||||
|
@ -472,19 +483,20 @@ public class mgrPrefs: NSObject {
|
||||||
case .invalidCharacters:
|
case .invalidCharacters:
|
||||||
return NSLocalizedString(
|
return NSLocalizedString(
|
||||||
"Candidate keys can only contain ASCII characters like alphanumericals.",
|
"Candidate keys can only contain ASCII characters like alphanumericals.",
|
||||||
comment: "")
|
comment: ""
|
||||||
|
)
|
||||||
case .containSpace:
|
case .containSpace:
|
||||||
return NSLocalizedString("Candidate keys cannot contain space.", comment: "")
|
return NSLocalizedString("Candidate keys cannot contain space.", comment: "")
|
||||||
case .duplicatedCharacters:
|
case .duplicatedCharacters:
|
||||||
return NSLocalizedString("There should not be duplicated keys.", comment: "")
|
return NSLocalizedString("There should not be duplicated keys.", comment: "")
|
||||||
case .tooShort:
|
case .tooShort:
|
||||||
return NSLocalizedString(
|
return NSLocalizedString(
|
||||||
"Please specify at least 4 candidate keys.", comment: "")
|
"Please specify at least 4 candidate keys.", comment: ""
|
||||||
|
)
|
||||||
case .tooLong:
|
case .tooLong:
|
||||||
return NSLocalizedString("Maximum 15 candidate keys allowed.", comment: "")
|
return NSLocalizedString("Maximum 15 candidate keys allowed.", comment: "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@UserDefault(key: UserDef.kPhraseReplacementEnabled, defaultValue: false)
|
@UserDefault(key: UserDef.kPhraseReplacementEnabled, defaultValue: false)
|
||||||
|
@ -505,5 +517,4 @@ public class mgrPrefs: NSObject {
|
||||||
UserDefaults.standard.set(associatedPhrasesEnabled, forKey: UserDef.kAssociatedPhrasesEnabled)
|
UserDefaults.standard.set(associatedPhrasesEnabled, forKey: UserDef.kAssociatedPhrasesEnabled)
|
||||||
return associatedPhrasesEnabled
|
return associatedPhrasesEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,13 +25,14 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
|
||||||
@objc extension mgrLangModel {
|
@objc extension mgrLangModel {
|
||||||
|
|
||||||
// MARK: - 獲取當前輸入法封包內的原廠核心語彙檔案所在路徑
|
// MARK: - 獲取當前輸入法封包內的原廠核心語彙檔案所在路徑
|
||||||
|
|
||||||
static func getBundleDataPath(_ filenameSansExt: String) -> String {
|
static func getBundleDataPath(_ filenameSansExt: String) -> String {
|
||||||
Bundle.main.path(forResource: filenameSansExt, ofType: "txt")!
|
Bundle.main.path(forResource: filenameSansExt, ofType: "txt")!
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 使用者語彙檔案的具體檔案名稱路徑定義
|
// MARK: - 使用者語彙檔案的具體檔案名稱路徑定義
|
||||||
|
|
||||||
// Swift 的 appendingPathComponent 需要藉由 URL 完成,最後再用 .path 轉為路徑。
|
// Swift 的 appendingPathComponent 需要藉由 URL 完成,最後再用 .path 轉為路徑。
|
||||||
|
|
||||||
static func userPhrasesDataPath(_ mode: InputMode) -> String {
|
static func userPhrasesDataPath(_ mode: InputMode) -> String {
|
||||||
|
@ -134,12 +135,12 @@ import Cocoa
|
||||||
// 發現目標路徑不是目錄的話:
|
// 發現目標路徑不是目錄的話:
|
||||||
// 如果要找的目標路徑是原廠目標路徑的話,先將這個路徑的所指對象更名、再認為目錄不存在。
|
// 如果要找的目標路徑是原廠目標路徑的話,先將這個路徑的所指對象更名、再認為目錄不存在。
|
||||||
// 如果要找的目標路徑不是原廠目標路徑的話,則直接報錯。
|
// 如果要找的目標路徑不是原廠目標路徑的話,則直接報錯。
|
||||||
if folderExist && !isFolder.boolValue {
|
if folderExist, !isFolder.boolValue {
|
||||||
do {
|
do {
|
||||||
if dataFolderPath(isDefaultFolder: false)
|
if dataFolderPath(isDefaultFolder: false)
|
||||||
== dataFolderPath(isDefaultFolder: true)
|
== dataFolderPath(isDefaultFolder: true)
|
||||||
{
|
{
|
||||||
let formatter = DateFormatter.init()
|
let formatter = DateFormatter()
|
||||||
formatter.dateFormat = "YYYYMMDD-HHMM'Hrs'-ss's'"
|
formatter.dateFormat = "YYYYMMDD-HHMM'Hrs'-ss's'"
|
||||||
let dirAlternative = folderPath + formatter.string(from: Date())
|
let dirAlternative = folderPath + formatter.string(from: Date())
|
||||||
try FileManager.default.moveItem(atPath: folderPath, toPath: dirAlternative)
|
try FileManager.default.moveItem(atPath: folderPath, toPath: dirAlternative)
|
||||||
|
@ -157,7 +158,8 @@ import Cocoa
|
||||||
try FileManager.default.createDirectory(
|
try FileManager.default.createDirectory(
|
||||||
atPath: folderPath,
|
atPath: folderPath,
|
||||||
withIntermediateDirectories: true,
|
withIntermediateDirectories: true,
|
||||||
attributes: nil)
|
attributes: nil
|
||||||
|
)
|
||||||
} catch {
|
} catch {
|
||||||
print("Failed to create folder: \(error)")
|
print("Failed to create folder: \(error)")
|
||||||
return false
|
return false
|
||||||
|
@ -167,6 +169,7 @@ import Cocoa
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 用以讀取使用者語彙檔案目錄的函數,會自動對 mgrPrefs 當中的參數糾偏。
|
// MARK: - 用以讀取使用者語彙檔案目錄的函數,會自動對 mgrPrefs 當中的參數糾偏。
|
||||||
|
|
||||||
// 當且僅當 mgrPrefs 當中的參數不合規(比如非實在路徑、或者無權限寫入)時,才會糾偏。
|
// 當且僅當 mgrPrefs 當中的參數不合規(比如非實在路徑、或者無權限寫入)時,才會糾偏。
|
||||||
|
|
||||||
static func dataFolderPath(isDefaultFolder: Bool) -> String {
|
static func dataFolderPath(isDefaultFolder: Bool) -> String {
|
||||||
|
@ -195,6 +198,7 @@ import Cocoa
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 寫入使用者檔案
|
// MARK: - 寫入使用者檔案
|
||||||
|
|
||||||
static func writeUserPhrase(
|
static func writeUserPhrase(
|
||||||
_ userPhrase: String?, inputMode mode: InputMode, areWeDuplicating: Bool, areWeDeleting: Bool
|
_ userPhrase: String?, inputMode mode: InputMode, areWeDuplicating: Bool, areWeDeleting: Bool
|
||||||
) -> Bool {
|
) -> Bool {
|
||||||
|
@ -207,7 +211,7 @@ import Cocoa
|
||||||
|
|
||||||
let path = areWeDeleting ? excludedPhrasesDataPath(mode) : userPhrasesDataPath(mode)
|
let path = areWeDeleting ? excludedPhrasesDataPath(mode) : userPhrasesDataPath(mode)
|
||||||
|
|
||||||
if areWeDuplicating && !areWeDeleting {
|
if areWeDuplicating, !areWeDeleting {
|
||||||
// Do not use ASCII characters to comment here.
|
// Do not use ASCII characters to comment here.
|
||||||
// Otherwise, it will be scrambled by cnvHYPYtoBPMF
|
// Otherwise, it will be scrambled by cnvHYPYtoBPMF
|
||||||
// module shipped in the vChewing Phrase Editor.
|
// module shipped in the vChewing Phrase Editor.
|
||||||
|
@ -238,5 +242,4 @@ import Cocoa
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,9 +28,10 @@ import Cocoa
|
||||||
|
|
||||||
public class clsSFX: NSObject, NSSoundDelegate {
|
public class clsSFX: NSObject, NSSoundDelegate {
|
||||||
private static let shared = clsSFX()
|
private static let shared = clsSFX()
|
||||||
private override init() {
|
override private init() {
|
||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
private var currentBeep: NSSound?
|
private var currentBeep: NSSound?
|
||||||
private func beep() {
|
private func beep() {
|
||||||
// Stop existing beep
|
// Stop existing beep
|
||||||
|
@ -57,9 +58,11 @@ public class clsSFX: NSObject, NSSoundDelegate {
|
||||||
beep.play()
|
beep.play()
|
||||||
currentBeep = beep
|
currentBeep = beep
|
||||||
}
|
}
|
||||||
@objc public func sound(_ sound: NSSound, didFinishPlaying flag: Bool) {
|
|
||||||
|
@objc public func sound(_: NSSound, didFinishPlaying _: Bool) {
|
||||||
currentBeep = nil
|
currentBeep = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc static func beep() {
|
@objc static func beep() {
|
||||||
shared.beep()
|
shared.beep()
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,8 @@ public protocol ctlCandidateDelegate: AnyObject {
|
||||||
func ctlCandidate(_ controller: ctlCandidate, candidateAtIndex index: UInt)
|
func ctlCandidate(_ controller: ctlCandidate, candidateAtIndex index: UInt)
|
||||||
-> String
|
-> String
|
||||||
func ctlCandidate(
|
func ctlCandidate(
|
||||||
_ controller: ctlCandidate, didSelectCandidateAtIndex index: UInt)
|
_ controller: ctlCandidate, didSelectCandidateAtIndex index: UInt
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc(ctlCandidate)
|
@objc(ctlCandidate)
|
||||||
|
@ -54,7 +55,8 @@ public class ctlCandidate: NSWindowController {
|
||||||
reloadData()
|
reloadData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@objc public var selectedCandidateIndex: UInt = UInt.max
|
|
||||||
|
@objc public var selectedCandidateIndex: UInt = .max
|
||||||
@objc public var visible: Bool = false {
|
@objc public var visible: Bool = false {
|
||||||
didSet {
|
didSet {
|
||||||
NSObject.cancelPreviousPerformRequests(withTarget: self)
|
NSObject.cancelPreviousPerformRequests(withTarget: self)
|
||||||
|
@ -65,6 +67,7 @@ public class ctlCandidate: NSWindowController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc public var windowTopLeftPoint: NSPoint {
|
@objc public var windowTopLeftPoint: NSPoint {
|
||||||
get {
|
get {
|
||||||
guard let frameRect = window?.frame else {
|
guard let frameRect = window?.frame else {
|
||||||
|
@ -83,13 +86,14 @@ public class ctlCandidate: NSWindowController {
|
||||||
.map {
|
.map {
|
||||||
CandidateKeyLabel(key: $0, displayedText: $0)
|
CandidateKeyLabel(key: $0, displayedText: $0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc public var keyLabelFont: NSFont = NSFont.monospacedDigitSystemFont(
|
@objc public var keyLabelFont: NSFont = NSFont.monospacedDigitSystemFont(
|
||||||
ofSize: 14, weight: .medium)
|
ofSize: 14, weight: .medium
|
||||||
|
)
|
||||||
@objc public var candidateFont: NSFont = NSFont.systemFont(ofSize: 18)
|
@objc public var candidateFont: NSFont = NSFont.systemFont(ofSize: 18)
|
||||||
@objc public var tooltip: String = ""
|
@objc public var tooltip: String = ""
|
||||||
|
|
||||||
@objc public func reloadData() {
|
@objc public func reloadData() {}
|
||||||
}
|
|
||||||
|
|
||||||
@objc public func showNextPage() -> Bool {
|
@objc public func showNextPage() -> Bool {
|
||||||
false
|
false
|
||||||
|
@ -107,7 +111,7 @@ public class ctlCandidate: NSWindowController {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc public func candidateIndexAtKeyLabelIndex(_ index: UInt) -> UInt {
|
@objc public func candidateIndexAtKeyLabelIndex(_: UInt) -> UInt {
|
||||||
UInt.max
|
UInt.max
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,7 +129,8 @@ public class ctlCandidate: NSWindowController {
|
||||||
public func set(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight height: CGFloat) {
|
public func set(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight height: CGFloat) {
|
||||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) {
|
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) {
|
||||||
self.doSet(
|
self.doSet(
|
||||||
windowTopLeftPoint: windowTopLeftPoint, bottomOutOfScreenAdjustmentHeight: height)
|
windowTopLeftPoint: windowTopLeftPoint, bottomOutOfScreenAdjustmentHeight: height
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,8 +141,8 @@ public class ctlCandidate: NSWindowController {
|
||||||
var screenFrame = NSScreen.main?.visibleFrame ?? NSRect.zero
|
var screenFrame = NSScreen.main?.visibleFrame ?? NSRect.zero
|
||||||
for screen in NSScreen.screens {
|
for screen in NSScreen.screens {
|
||||||
let frame = screen.visibleFrame
|
let frame = screen.visibleFrame
|
||||||
if windowTopLeftPoint.x >= frame.minX && windowTopLeftPoint.x <= frame.maxX
|
if windowTopLeftPoint.x >= frame.minX, windowTopLeftPoint.x <= frame.maxX,
|
||||||
&& windowTopLeftPoint.y >= frame.minY && windowTopLeftPoint.y <= frame.maxY
|
windowTopLeftPoint.y >= frame.minY, windowTopLeftPoint.y <= frame.maxY
|
||||||
{
|
{
|
||||||
screenFrame = frame
|
screenFrame = frame
|
||||||
break
|
break
|
||||||
|
@ -172,5 +177,4 @@ public class ctlCandidate: NSWindowController {
|
||||||
|
|
||||||
window?.setFrameTopLeftPoint(adjustedPoint)
|
window?.setFrameTopLeftPoint(adjustedPoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,7 @@ private class HorizontalCandidateView: NSView {
|
||||||
private var candidateAttrDict: [NSAttributedString.Key: AnyObject] = [:]
|
private var candidateAttrDict: [NSAttributedString.Key: AnyObject] = [:]
|
||||||
private var candidateWithLabelAttrDict: [NSAttributedString.Key: AnyObject] = [:]
|
private var candidateWithLabelAttrDict: [NSAttributedString.Key: AnyObject] = [:]
|
||||||
private var elementWidths: [CGFloat] = []
|
private var elementWidths: [CGFloat] = []
|
||||||
private var trackingHighlightedIndex: UInt = UInt.max
|
private var trackingHighlightedIndex: UInt = .max
|
||||||
|
|
||||||
override var isFlipped: Bool {
|
override var isFlipped: Bool {
|
||||||
true
|
true
|
||||||
|
@ -71,7 +71,8 @@ private class HorizontalCandidateView: NSView {
|
||||||
for index in 0..<count {
|
for index in 0..<count {
|
||||||
let rctCandidate = (dispCandidatesWithLabels[index] as NSString).boundingRect(
|
let rctCandidate = (dispCandidatesWithLabels[index] as NSString).boundingRect(
|
||||||
with: baseSize, options: .usesLineFragmentOrigin,
|
with: baseSize, options: .usesLineFragmentOrigin,
|
||||||
attributes: candidateWithLabelAttrDict)
|
attributes: candidateWithLabelAttrDict
|
||||||
|
)
|
||||||
var cellWidth = rctCandidate.size.width + cellPadding
|
var cellWidth = rctCandidate.size.width + cellPadding
|
||||||
let cellHeight = rctCandidate.size.height + cellPadding
|
let cellHeight = rctCandidate.size.height + cellPadding
|
||||||
if cellWidth < cellHeight * 1.35 {
|
if cellWidth < cellHeight * 1.35 {
|
||||||
|
@ -115,7 +116,7 @@ private class HorizontalCandidateView: NSView {
|
||||||
cellPadding = ceil(biggestSize / 2.0)
|
cellPadding = ceil(biggestSize / 2.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func draw(_ dirtyRect: NSRect) {
|
override func draw(_: NSRect) {
|
||||||
let bounds = bounds
|
let bounds = bounds
|
||||||
NSColor.controlBackgroundColor.setFill() // Candidate list panel base background
|
NSColor.controlBackgroundColor.setFill() // Candidate list panel base background
|
||||||
NSBezierPath.fill(bounds)
|
NSBezierPath.fill(bounds)
|
||||||
|
@ -124,21 +125,25 @@ private class HorizontalCandidateView: NSView {
|
||||||
|
|
||||||
NSBezierPath.strokeLine(
|
NSBezierPath.strokeLine(
|
||||||
from: NSPoint(x: bounds.size.width, y: 0.0),
|
from: NSPoint(x: bounds.size.width, y: 0.0),
|
||||||
to: NSPoint(x: bounds.size.width, y: bounds.size.height))
|
to: NSPoint(x: bounds.size.width, y: bounds.size.height)
|
||||||
|
)
|
||||||
|
|
||||||
var accuWidth: CGFloat = 0
|
var accuWidth: CGFloat = 0
|
||||||
for index in 0..<elementWidths.count {
|
for index in 0..<elementWidths.count {
|
||||||
let currentWidth = elementWidths[index]
|
let currentWidth = elementWidths[index]
|
||||||
let rctCandidateArea = NSRect(
|
let rctCandidateArea = NSRect(
|
||||||
x: accuWidth, y: 0.0, width: currentWidth + 1.0,
|
x: accuWidth, y: 0.0, width: currentWidth + 1.0,
|
||||||
height: candidateTextHeight + cellPadding)
|
height: candidateTextHeight + cellPadding
|
||||||
|
)
|
||||||
let rctLabel = NSRect(
|
let rctLabel = NSRect(
|
||||||
x: accuWidth + cellPadding / 2 - 1, y: cellPadding / 2, width: keyLabelWidth,
|
x: accuWidth + cellPadding / 2 - 1, y: cellPadding / 2, width: keyLabelWidth,
|
||||||
height: keyLabelHeight * 2.0)
|
height: keyLabelHeight * 2.0
|
||||||
|
)
|
||||||
let rctCandidatePhrase = NSRect(
|
let rctCandidatePhrase = NSRect(
|
||||||
x: accuWidth + keyLabelWidth - 1, y: cellPadding / 2,
|
x: accuWidth + keyLabelWidth - 1, y: cellPadding / 2,
|
||||||
width: currentWidth - keyLabelWidth,
|
width: currentWidth - keyLabelWidth,
|
||||||
height: candidateTextHeight)
|
height: candidateTextHeight
|
||||||
|
)
|
||||||
|
|
||||||
var activeCandidateIndexAttr = keyLabelAttrDict
|
var activeCandidateIndexAttr = keyLabelAttrDict
|
||||||
var activeCandidateAttr = candidateAttrDict
|
var activeCandidateAttr = candidateAttrDict
|
||||||
|
@ -149,13 +154,15 @@ private class HorizontalCandidateView: NSView {
|
||||||
case InputMode.imeModeCHS:
|
case InputMode.imeModeCHS:
|
||||||
NSColor.systemRed.blended(
|
NSColor.systemRed.blended(
|
||||||
withFraction: colorBlendAmount,
|
withFraction: colorBlendAmount,
|
||||||
of: NSColor.controlBackgroundColor)!
|
of: NSColor.controlBackgroundColor
|
||||||
.setFill()
|
)!
|
||||||
|
.setFill()
|
||||||
case InputMode.imeModeCHT:
|
case InputMode.imeModeCHT:
|
||||||
NSColor.systemBlue.blended(
|
NSColor.systemBlue.blended(
|
||||||
withFraction: colorBlendAmount,
|
withFraction: colorBlendAmount,
|
||||||
of: NSColor.controlBackgroundColor)!
|
of: NSColor.controlBackgroundColor
|
||||||
.setFill()
|
)!
|
||||||
|
.setFill()
|
||||||
default:
|
default:
|
||||||
NSColor.alternateSelectedControlColor.setFill()
|
NSColor.alternateSelectedControlColor.setFill()
|
||||||
}
|
}
|
||||||
|
@ -181,9 +188,11 @@ private class HorizontalCandidateView: NSView {
|
||||||
}
|
}
|
||||||
NSBezierPath.fill(rctCandidateArea)
|
NSBezierPath.fill(rctCandidateArea)
|
||||||
(keyLabels[index] as NSString).draw(
|
(keyLabels[index] as NSString).draw(
|
||||||
in: rctLabel, withAttributes: activeCandidateIndexAttr)
|
in: rctLabel, withAttributes: activeCandidateIndexAttr
|
||||||
|
)
|
||||||
(displayedCandidates[index] as NSString).draw(
|
(displayedCandidates[index] as NSString).draw(
|
||||||
in: rctCandidatePhrase, withAttributes: activeCandidateAttr)
|
in: rctCandidatePhrase, withAttributes: activeCandidateAttr
|
||||||
|
)
|
||||||
accuWidth += currentWidth + 1.0
|
accuWidth += currentWidth + 1.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -197,13 +206,12 @@ private class HorizontalCandidateView: NSView {
|
||||||
for index in 0..<elementWidths.count {
|
for index in 0..<elementWidths.count {
|
||||||
let currentWidth = elementWidths[index]
|
let currentWidth = elementWidths[index]
|
||||||
|
|
||||||
if location.x >= accuWidth && location.x <= accuWidth + currentWidth {
|
if location.x >= accuWidth, location.x <= accuWidth + currentWidth {
|
||||||
return UInt(index)
|
return UInt(index)
|
||||||
}
|
}
|
||||||
accuWidth += currentWidth + 1.0
|
accuWidth += currentWidth + 1.0
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override func mouseUp(with event: NSEvent) {
|
override func mouseUp(with event: NSEvent) {
|
||||||
|
@ -246,7 +254,8 @@ public class ctlCandidateHorizontal: ctlCandidate {
|
||||||
var contentRect = NSRect(x: 128.0, y: 128.0, width: 0.0, height: 0.0)
|
var contentRect = NSRect(x: 128.0, y: 128.0, width: 0.0, height: 0.0)
|
||||||
let styleMask: NSWindow.StyleMask = [.nonactivatingPanel]
|
let styleMask: NSWindow.StyleMask = [.nonactivatingPanel]
|
||||||
let panel = NSPanel(
|
let panel = NSPanel(
|
||||||
contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false)
|
contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false
|
||||||
|
)
|
||||||
panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel) + 1)
|
panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel) + 1)
|
||||||
panel.hasShadow = true
|
panel.hasShadow = true
|
||||||
panel.isOpaque = false
|
panel.isOpaque = false
|
||||||
|
@ -279,7 +288,8 @@ public class ctlCandidateHorizontal: ctlCandidate {
|
||||||
nextPageButton.bezelStyle = .disclosure
|
nextPageButton.bezelStyle = .disclosure
|
||||||
nextPageButton.userInterfaceLayoutDirection = .leftToRight
|
nextPageButton.userInterfaceLayoutDirection = .leftToRight
|
||||||
nextPageButton.attributedTitle = NSMutableAttributedString(
|
nextPageButton.attributedTitle = NSMutableAttributedString(
|
||||||
string: " ", attributes: buttonAttribute) // Next Page Arrow
|
string: " ", attributes: buttonAttribute
|
||||||
|
) // Next Page Arrow
|
||||||
prevPageButton = NSButton(frame: contentRect)
|
prevPageButton = NSButton(frame: contentRect)
|
||||||
NSColor.controlBackgroundColor.setFill()
|
NSColor.controlBackgroundColor.setFill()
|
||||||
NSBezierPath.fill(prevPageButton.bounds)
|
NSBezierPath.fill(prevPageButton.bounds)
|
||||||
|
@ -291,7 +301,8 @@ public class ctlCandidateHorizontal: ctlCandidate {
|
||||||
prevPageButton.bezelStyle = .disclosure
|
prevPageButton.bezelStyle = .disclosure
|
||||||
prevPageButton.userInterfaceLayoutDirection = .rightToLeft
|
prevPageButton.userInterfaceLayoutDirection = .rightToLeft
|
||||||
prevPageButton.attributedTitle = NSMutableAttributedString(
|
prevPageButton.attributedTitle = NSMutableAttributedString(
|
||||||
string: " ", attributes: buttonAttribute) // Previous Page Arrow
|
string: " ", attributes: buttonAttribute
|
||||||
|
) // Previous Page Arrow
|
||||||
panel.contentView?.addSubview(nextPageButton)
|
panel.contentView?.addSubview(nextPageButton)
|
||||||
panel.contentView?.addSubview(prevPageButton)
|
panel.contentView?.addSubview(prevPageButton)
|
||||||
|
|
||||||
|
@ -307,17 +318,18 @@ public class ctlCandidateHorizontal: ctlCandidate {
|
||||||
prevPageButton.action = #selector(pageButtonAction(_:))
|
prevPageButton.action = #selector(pageButtonAction(_:))
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
@available(*, unavailable)
|
||||||
|
required init?(coder _: NSCoder) {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
public override func reloadData() {
|
override public func reloadData() {
|
||||||
candidateView.highlightedIndex = 0
|
candidateView.highlightedIndex = 0
|
||||||
currentPage = 0
|
currentPage = 0
|
||||||
layoutCandidateView()
|
layoutCandidateView()
|
||||||
}
|
}
|
||||||
|
|
||||||
public override func showNextPage() -> Bool {
|
override public func showNextPage() -> Bool {
|
||||||
guard delegate != nil else { return false }
|
guard delegate != nil else { return false }
|
||||||
if pageCount == 1 { return highlightNextCandidate() }
|
if pageCount == 1 { return highlightNextCandidate() }
|
||||||
currentPage = (currentPage + 1 >= pageCount) ? 0 : currentPage + 1
|
currentPage = (currentPage + 1 >= pageCount) ? 0 : currentPage + 1
|
||||||
|
@ -326,7 +338,7 @@ public class ctlCandidateHorizontal: ctlCandidate {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
public override func showPreviousPage() -> Bool {
|
override public func showPreviousPage() -> Bool {
|
||||||
guard delegate != nil else { return false }
|
guard delegate != nil else { return false }
|
||||||
if pageCount == 1 { return highlightPreviousCandidate() }
|
if pageCount == 1 { return highlightPreviousCandidate() }
|
||||||
currentPage = (currentPage == 0) ? pageCount - 1 : currentPage - 1
|
currentPage = (currentPage == 0) ? pageCount - 1 : currentPage - 1
|
||||||
|
@ -335,7 +347,7 @@ public class ctlCandidateHorizontal: ctlCandidate {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
public override func highlightNextCandidate() -> Bool {
|
override public func highlightNextCandidate() -> Bool {
|
||||||
guard let delegate = delegate else { return false }
|
guard let delegate = delegate else { return false }
|
||||||
selectedCandidateIndex =
|
selectedCandidateIndex =
|
||||||
(selectedCandidateIndex + 1 >= delegate.candidateCountForController(self))
|
(selectedCandidateIndex + 1 >= delegate.candidateCountForController(self))
|
||||||
|
@ -343,7 +355,7 @@ public class ctlCandidateHorizontal: ctlCandidate {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
public override func highlightPreviousCandidate() -> Bool {
|
override public func highlightPreviousCandidate() -> Bool {
|
||||||
guard let delegate = delegate else { return false }
|
guard let delegate = delegate else { return false }
|
||||||
selectedCandidateIndex =
|
selectedCandidateIndex =
|
||||||
(selectedCandidateIndex == 0)
|
(selectedCandidateIndex == 0)
|
||||||
|
@ -351,7 +363,7 @@ public class ctlCandidateHorizontal: ctlCandidate {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
public override func candidateIndexAtKeyLabelIndex(_ index: UInt) -> UInt {
|
override public func candidateIndexAtKeyLabelIndex(_ index: UInt) -> UInt {
|
||||||
guard let delegate = delegate else {
|
guard let delegate = delegate else {
|
||||||
return UInt.max
|
return UInt.max
|
||||||
}
|
}
|
||||||
|
@ -360,7 +372,7 @@ public class ctlCandidateHorizontal: ctlCandidate {
|
||||||
return result < delegate.candidateCountForController(self) ? result : UInt.max
|
return result < delegate.candidateCountForController(self) ? result : UInt.max
|
||||||
}
|
}
|
||||||
|
|
||||||
public override var selectedCandidateIndex: UInt {
|
override public var selectedCandidateIndex: UInt {
|
||||||
get {
|
get {
|
||||||
currentPage * UInt(keyLabels.count) + candidateView.highlightedIndex
|
currentPage * UInt(keyLabels.count) + candidateView.highlightedIndex
|
||||||
}
|
}
|
||||||
|
@ -379,7 +391,6 @@ public class ctlCandidateHorizontal: ctlCandidate {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ctlCandidateHorizontal {
|
extension ctlCandidateHorizontal {
|
||||||
|
|
||||||
private var pageCount: UInt {
|
private var pageCount: UInt {
|
||||||
guard let delegate = delegate else {
|
guard let delegate = delegate else {
|
||||||
return 0
|
return 0
|
||||||
|
@ -405,13 +416,14 @@ extension ctlCandidateHorizontal {
|
||||||
candidates.append(candidate)
|
candidates.append(candidate)
|
||||||
}
|
}
|
||||||
candidateView.set(
|
candidateView.set(
|
||||||
keyLabels: keyLabels.map { $0.displayedText }, displayedCandidates: candidates)
|
keyLabels: keyLabels.map(\.displayedText), displayedCandidates: candidates
|
||||||
|
)
|
||||||
var newSize = candidateView.sizeForView
|
var newSize = candidateView.sizeForView
|
||||||
var frameRect = candidateView.frame
|
var frameRect = candidateView.frame
|
||||||
frameRect.size = newSize
|
frameRect.size = newSize
|
||||||
candidateView.frame = frameRect
|
candidateView.frame = frameRect
|
||||||
|
|
||||||
if pageCount > 1 && mgrPrefs.showPageButtonsInCandidateWindow {
|
if pageCount > 1, mgrPrefs.showPageButtonsInCandidateWindow {
|
||||||
var buttonRect = nextPageButton.frame
|
var buttonRect = nextPageButton.frame
|
||||||
let spacing: CGFloat = 0.0
|
let spacing: CGFloat = 0.0
|
||||||
|
|
||||||
|
@ -422,7 +434,8 @@ extension ctlCandidateHorizontal {
|
||||||
nextPageButton.frame = buttonRect
|
nextPageButton.frame = buttonRect
|
||||||
|
|
||||||
buttonRect.origin = NSPoint(
|
buttonRect.origin = NSPoint(
|
||||||
x: newSize.width, y: buttonOriginY + buttonRect.size.height + spacing)
|
x: newSize.width, y: buttonOriginY + buttonRect.size.height + spacing
|
||||||
|
)
|
||||||
prevPageButton.frame = buttonRect
|
prevPageButton.frame = buttonRect
|
||||||
|
|
||||||
newSize.width += 20
|
newSize.width += 20
|
||||||
|
@ -442,7 +455,7 @@ extension ctlCandidateHorizontal {
|
||||||
candidateView.setNeedsDisplay(candidateView.bounds)
|
candidateView.setNeedsDisplay(candidateView.bounds)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc fileprivate func pageButtonAction(_ sender: Any) {
|
@objc private func pageButtonAction(_ sender: Any) {
|
||||||
guard let sender = sender as? NSButton else {
|
guard let sender = sender as? NSButton else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -453,8 +466,7 @@ extension ctlCandidateHorizontal {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc fileprivate func candidateViewMouseDidClick(_ sender: Any) {
|
@objc private func candidateViewMouseDidClick(_: Any) {
|
||||||
delegate?.ctlCandidate(self, didSelectCandidateAtIndex: selectedCandidateIndex)
|
delegate?.ctlCandidate(self, didSelectCandidateAtIndex: selectedCandidateIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ private class VerticalCandidateView: NSView {
|
||||||
private var windowWidth: CGFloat = 0
|
private var windowWidth: CGFloat = 0
|
||||||
private var elementWidths: [CGFloat] = []
|
private var elementWidths: [CGFloat] = []
|
||||||
private var elementHeights: [CGFloat] = []
|
private var elementHeights: [CGFloat] = []
|
||||||
private var trackingHighlightedIndex: UInt = UInt.max
|
private var trackingHighlightedIndex: UInt = .max
|
||||||
|
|
||||||
override var isFlipped: Bool {
|
override var isFlipped: Bool {
|
||||||
true
|
true
|
||||||
|
@ -74,7 +74,8 @@ private class VerticalCandidateView: NSView {
|
||||||
for index in 0..<count {
|
for index in 0..<count {
|
||||||
let rctCandidate = (dispCandidatesWithLabels[index] as NSString).boundingRect(
|
let rctCandidate = (dispCandidatesWithLabels[index] as NSString).boundingRect(
|
||||||
with: baseSize, options: .usesLineFragmentOrigin,
|
with: baseSize, options: .usesLineFragmentOrigin,
|
||||||
attributes: candidateWithLabelAttrDict)
|
attributes: candidateWithLabelAttrDict
|
||||||
|
)
|
||||||
let cellWidth = rctCandidate.size.width + cellPadding
|
let cellWidth = rctCandidate.size.width + cellPadding
|
||||||
let cellHeight = rctCandidate.size.height + cellPadding
|
let cellHeight = rctCandidate.size.height + cellPadding
|
||||||
if calculatedWindowWidth < rctCandidate.size.width {
|
if calculatedWindowWidth < rctCandidate.size.width {
|
||||||
|
@ -121,7 +122,7 @@ private class VerticalCandidateView: NSView {
|
||||||
cellPadding = ceil(biggestSize / 2.0)
|
cellPadding = ceil(biggestSize / 2.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func draw(_ dirtyRect: NSRect) {
|
override func draw(_: NSRect) {
|
||||||
let bounds = bounds
|
let bounds = bounds
|
||||||
NSColor.controlBackgroundColor.setFill() // Candidate list panel base background
|
NSColor.controlBackgroundColor.setFill() // Candidate list panel base background
|
||||||
NSBezierPath.fill(bounds)
|
NSBezierPath.fill(bounds)
|
||||||
|
@ -130,7 +131,8 @@ private class VerticalCandidateView: NSView {
|
||||||
|
|
||||||
NSBezierPath.strokeLine(
|
NSBezierPath.strokeLine(
|
||||||
from: NSPoint(x: bounds.size.width, y: 0.0),
|
from: NSPoint(x: bounds.size.width, y: 0.0),
|
||||||
to: NSPoint(x: bounds.size.width, y: bounds.size.height))
|
to: NSPoint(x: bounds.size.width, y: bounds.size.height)
|
||||||
|
)
|
||||||
|
|
||||||
var accuHeight: CGFloat = 0
|
var accuHeight: CGFloat = 0
|
||||||
for index in 0..<elementHeights.count {
|
for index in 0..<elementHeights.count {
|
||||||
|
@ -140,10 +142,12 @@ private class VerticalCandidateView: NSView {
|
||||||
)
|
)
|
||||||
let rctLabel = NSRect(
|
let rctLabel = NSRect(
|
||||||
x: cellPadding / 2 - 1, y: accuHeight + cellPadding / 2, width: keyLabelWidth,
|
x: cellPadding / 2 - 1, y: accuHeight + cellPadding / 2, width: keyLabelWidth,
|
||||||
height: keyLabelHeight * 2.0)
|
height: keyLabelHeight * 2.0
|
||||||
|
)
|
||||||
let rctCandidatePhrase = NSRect(
|
let rctCandidatePhrase = NSRect(
|
||||||
x: cellPadding / 2 - 1 + keyLabelWidth, y: accuHeight + cellPadding / 2 - 1,
|
x: cellPadding / 2 - 1 + keyLabelWidth, y: accuHeight + cellPadding / 2 - 1,
|
||||||
width: windowWidth - keyLabelWidth, height: candidateTextHeight)
|
width: windowWidth - keyLabelWidth, height: candidateTextHeight
|
||||||
|
)
|
||||||
|
|
||||||
var activeCandidateIndexAttr = keyLabelAttrDict
|
var activeCandidateIndexAttr = keyLabelAttrDict
|
||||||
var activeCandidateAttr = candidateAttrDict
|
var activeCandidateAttr = candidateAttrDict
|
||||||
|
@ -154,13 +158,15 @@ private class VerticalCandidateView: NSView {
|
||||||
case InputMode.imeModeCHS:
|
case InputMode.imeModeCHS:
|
||||||
NSColor.systemRed.blended(
|
NSColor.systemRed.blended(
|
||||||
withFraction: colorBlendAmount,
|
withFraction: colorBlendAmount,
|
||||||
of: NSColor.controlBackgroundColor)!
|
of: NSColor.controlBackgroundColor
|
||||||
.setFill()
|
)!
|
||||||
|
.setFill()
|
||||||
case InputMode.imeModeCHT:
|
case InputMode.imeModeCHT:
|
||||||
NSColor.systemBlue.blended(
|
NSColor.systemBlue.blended(
|
||||||
withFraction: colorBlendAmount,
|
withFraction: colorBlendAmount,
|
||||||
of: NSColor.controlBackgroundColor)!
|
of: NSColor.controlBackgroundColor
|
||||||
.setFill()
|
)!
|
||||||
|
.setFill()
|
||||||
default:
|
default:
|
||||||
NSColor.alternateSelectedControlColor.setFill()
|
NSColor.alternateSelectedControlColor.setFill()
|
||||||
}
|
}
|
||||||
|
@ -186,9 +192,11 @@ private class VerticalCandidateView: NSView {
|
||||||
}
|
}
|
||||||
NSBezierPath.fill(rctCandidateArea)
|
NSBezierPath.fill(rctCandidateArea)
|
||||||
(keyLabels[index] as NSString).draw(
|
(keyLabels[index] as NSString).draw(
|
||||||
in: rctLabel, withAttributes: activeCandidateIndexAttr)
|
in: rctLabel, withAttributes: activeCandidateIndexAttr
|
||||||
|
)
|
||||||
(displayedCandidates[index] as NSString).draw(
|
(displayedCandidates[index] as NSString).draw(
|
||||||
in: rctCandidatePhrase, withAttributes: activeCandidateAttr)
|
in: rctCandidatePhrase, withAttributes: activeCandidateAttr
|
||||||
|
)
|
||||||
accuHeight += currentHeight
|
accuHeight += currentHeight
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -202,13 +210,12 @@ private class VerticalCandidateView: NSView {
|
||||||
for index in 0..<elementHeights.count {
|
for index in 0..<elementHeights.count {
|
||||||
let currentHeight = elementHeights[index]
|
let currentHeight = elementHeights[index]
|
||||||
|
|
||||||
if location.y >= accuHeight && location.y <= accuHeight + currentHeight {
|
if location.y >= accuHeight, location.y <= accuHeight + currentHeight {
|
||||||
return UInt(index)
|
return UInt(index)
|
||||||
}
|
}
|
||||||
accuHeight += currentHeight
|
accuHeight += currentHeight
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override func mouseUp(with event: NSEvent) {
|
override func mouseUp(with event: NSEvent) {
|
||||||
|
@ -251,7 +258,8 @@ public class ctlCandidateVertical: ctlCandidate {
|
||||||
var contentRect = NSRect(x: 128.0, y: 128.0, width: 0.0, height: 0.0)
|
var contentRect = NSRect(x: 128.0, y: 128.0, width: 0.0, height: 0.0)
|
||||||
let styleMask: NSWindow.StyleMask = [.nonactivatingPanel]
|
let styleMask: NSWindow.StyleMask = [.nonactivatingPanel]
|
||||||
let panel = NSPanel(
|
let panel = NSPanel(
|
||||||
contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false)
|
contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false
|
||||||
|
)
|
||||||
panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel) + 1)
|
panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel) + 1)
|
||||||
panel.hasShadow = true
|
panel.hasShadow = true
|
||||||
panel.isOpaque = false
|
panel.isOpaque = false
|
||||||
|
@ -284,7 +292,8 @@ public class ctlCandidateVertical: ctlCandidate {
|
||||||
nextPageButton.bezelStyle = .disclosure
|
nextPageButton.bezelStyle = .disclosure
|
||||||
nextPageButton.userInterfaceLayoutDirection = .leftToRight
|
nextPageButton.userInterfaceLayoutDirection = .leftToRight
|
||||||
nextPageButton.attributedTitle = NSMutableAttributedString(
|
nextPageButton.attributedTitle = NSMutableAttributedString(
|
||||||
string: " ", attributes: buttonAttribute) // Next Page Arrow
|
string: " ", attributes: buttonAttribute
|
||||||
|
) // Next Page Arrow
|
||||||
prevPageButton = NSButton(frame: contentRect)
|
prevPageButton = NSButton(frame: contentRect)
|
||||||
NSColor.controlBackgroundColor.setFill()
|
NSColor.controlBackgroundColor.setFill()
|
||||||
NSBezierPath.fill(prevPageButton.bounds)
|
NSBezierPath.fill(prevPageButton.bounds)
|
||||||
|
@ -296,7 +305,8 @@ public class ctlCandidateVertical: ctlCandidate {
|
||||||
prevPageButton.bezelStyle = .disclosure
|
prevPageButton.bezelStyle = .disclosure
|
||||||
prevPageButton.userInterfaceLayoutDirection = .rightToLeft
|
prevPageButton.userInterfaceLayoutDirection = .rightToLeft
|
||||||
prevPageButton.attributedTitle = NSMutableAttributedString(
|
prevPageButton.attributedTitle = NSMutableAttributedString(
|
||||||
string: " ", attributes: buttonAttribute) // Previous Page Arrow
|
string: " ", attributes: buttonAttribute
|
||||||
|
) // Previous Page Arrow
|
||||||
panel.contentView?.addSubview(nextPageButton)
|
panel.contentView?.addSubview(nextPageButton)
|
||||||
panel.contentView?.addSubview(prevPageButton)
|
panel.contentView?.addSubview(prevPageButton)
|
||||||
|
|
||||||
|
@ -312,17 +322,18 @@ public class ctlCandidateVertical: ctlCandidate {
|
||||||
prevPageButton.action = #selector(pageButtonAction(_:))
|
prevPageButton.action = #selector(pageButtonAction(_:))
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
@available(*, unavailable)
|
||||||
|
required init?(coder _: NSCoder) {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
public override func reloadData() {
|
override public func reloadData() {
|
||||||
candidateView.highlightedIndex = 0
|
candidateView.highlightedIndex = 0
|
||||||
currentPage = 0
|
currentPage = 0
|
||||||
layoutCandidateView()
|
layoutCandidateView()
|
||||||
}
|
}
|
||||||
|
|
||||||
public override func showNextPage() -> Bool {
|
override public func showNextPage() -> Bool {
|
||||||
guard delegate != nil else { return false }
|
guard delegate != nil else { return false }
|
||||||
if pageCount == 1 { return highlightNextCandidate() }
|
if pageCount == 1 { return highlightNextCandidate() }
|
||||||
currentPage = (currentPage + 1 >= pageCount) ? 0 : currentPage + 1
|
currentPage = (currentPage + 1 >= pageCount) ? 0 : currentPage + 1
|
||||||
|
@ -331,7 +342,7 @@ public class ctlCandidateVertical: ctlCandidate {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
public override func showPreviousPage() -> Bool {
|
override public func showPreviousPage() -> Bool {
|
||||||
guard delegate != nil else { return false }
|
guard delegate != nil else { return false }
|
||||||
if pageCount == 1 { return highlightPreviousCandidate() }
|
if pageCount == 1 { return highlightPreviousCandidate() }
|
||||||
currentPage = (currentPage == 0) ? pageCount - 1 : currentPage - 1
|
currentPage = (currentPage == 0) ? pageCount - 1 : currentPage - 1
|
||||||
|
@ -340,7 +351,7 @@ public class ctlCandidateVertical: ctlCandidate {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
public override func highlightNextCandidate() -> Bool {
|
override public func highlightNextCandidate() -> Bool {
|
||||||
guard let delegate = delegate else { return false }
|
guard let delegate = delegate else { return false }
|
||||||
selectedCandidateIndex =
|
selectedCandidateIndex =
|
||||||
(selectedCandidateIndex + 1 >= delegate.candidateCountForController(self))
|
(selectedCandidateIndex + 1 >= delegate.candidateCountForController(self))
|
||||||
|
@ -348,7 +359,7 @@ public class ctlCandidateVertical: ctlCandidate {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
public override func highlightPreviousCandidate() -> Bool {
|
override public func highlightPreviousCandidate() -> Bool {
|
||||||
guard let delegate = delegate else { return false }
|
guard let delegate = delegate else { return false }
|
||||||
selectedCandidateIndex =
|
selectedCandidateIndex =
|
||||||
(selectedCandidateIndex == 0)
|
(selectedCandidateIndex == 0)
|
||||||
|
@ -356,7 +367,7 @@ public class ctlCandidateVertical: ctlCandidate {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
public override func candidateIndexAtKeyLabelIndex(_ index: UInt) -> UInt {
|
override public func candidateIndexAtKeyLabelIndex(_ index: UInt) -> UInt {
|
||||||
guard let delegate = delegate else {
|
guard let delegate = delegate else {
|
||||||
return UInt.max
|
return UInt.max
|
||||||
}
|
}
|
||||||
|
@ -365,7 +376,7 @@ public class ctlCandidateVertical: ctlCandidate {
|
||||||
return result < delegate.candidateCountForController(self) ? result : UInt.max
|
return result < delegate.candidateCountForController(self) ? result : UInt.max
|
||||||
}
|
}
|
||||||
|
|
||||||
public override var selectedCandidateIndex: UInt {
|
override public var selectedCandidateIndex: UInt {
|
||||||
get {
|
get {
|
||||||
currentPage * UInt(keyLabels.count) + candidateView.highlightedIndex
|
currentPage * UInt(keyLabels.count) + candidateView.highlightedIndex
|
||||||
}
|
}
|
||||||
|
@ -384,7 +395,6 @@ public class ctlCandidateVertical: ctlCandidate {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ctlCandidateVertical {
|
extension ctlCandidateVertical {
|
||||||
|
|
||||||
private var pageCount: UInt {
|
private var pageCount: UInt {
|
||||||
guard let delegate = delegate else {
|
guard let delegate = delegate else {
|
||||||
return 0
|
return 0
|
||||||
|
@ -410,13 +420,14 @@ extension ctlCandidateVertical {
|
||||||
candidates.append(candidate)
|
candidates.append(candidate)
|
||||||
}
|
}
|
||||||
candidateView.set(
|
candidateView.set(
|
||||||
keyLabels: keyLabels.map { $0.displayedText }, displayedCandidates: candidates)
|
keyLabels: keyLabels.map(\.displayedText), displayedCandidates: candidates
|
||||||
|
)
|
||||||
var newSize = candidateView.sizeForView
|
var newSize = candidateView.sizeForView
|
||||||
var frameRect = candidateView.frame
|
var frameRect = candidateView.frame
|
||||||
frameRect.size = newSize
|
frameRect.size = newSize
|
||||||
candidateView.frame = frameRect
|
candidateView.frame = frameRect
|
||||||
|
|
||||||
if pageCount > 1 && mgrPrefs.showPageButtonsInCandidateWindow {
|
if pageCount > 1, mgrPrefs.showPageButtonsInCandidateWindow {
|
||||||
var buttonRect = nextPageButton.frame
|
var buttonRect = nextPageButton.frame
|
||||||
let spacing: CGFloat = 0.0
|
let spacing: CGFloat = 0.0
|
||||||
|
|
||||||
|
@ -427,7 +438,8 @@ extension ctlCandidateVertical {
|
||||||
nextPageButton.frame = buttonRect
|
nextPageButton.frame = buttonRect
|
||||||
|
|
||||||
buttonRect.origin = NSPoint(
|
buttonRect.origin = NSPoint(
|
||||||
x: newSize.width, y: buttonOriginY + buttonRect.size.height + spacing)
|
x: newSize.width, y: buttonOriginY + buttonRect.size.height + spacing
|
||||||
|
)
|
||||||
prevPageButton.frame = buttonRect
|
prevPageButton.frame = buttonRect
|
||||||
|
|
||||||
newSize.width += 20
|
newSize.width += 20
|
||||||
|
@ -447,7 +459,7 @@ extension ctlCandidateVertical {
|
||||||
candidateView.setNeedsDisplay(candidateView.bounds)
|
candidateView.setNeedsDisplay(candidateView.bounds)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc fileprivate func pageButtonAction(_ sender: Any) {
|
@objc private func pageButtonAction(_ sender: Any) {
|
||||||
guard let sender = sender as? NSButton else {
|
guard let sender = sender as? NSButton else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -458,8 +470,7 @@ extension ctlCandidateVertical {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc fileprivate func candidateViewMouseDidClick(_ sender: Any) {
|
@objc private func candidateViewMouseDidClick(_: Any) {
|
||||||
delegate?.ctlCandidate(self, didSelectCandidateAtIndex: selectedCandidateIndex)
|
delegate?.ctlCandidate(self, didSelectCandidateAtIndex: selectedCandidateIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@ private protocol NotifierWindowDelegate: AnyObject {
|
||||||
private class NotifierWindow: NSWindow {
|
private class NotifierWindow: NSWindow {
|
||||||
weak var clickDelegate: NotifierWindowDelegate?
|
weak var clickDelegate: NotifierWindowDelegate?
|
||||||
|
|
||||||
override func mouseDown(with event: NSEvent) {
|
override func mouseDown(with _: NSEvent) {
|
||||||
clickDelegate?.windowDidBecomeClicked(self)
|
clickDelegate?.windowDidBecomeClicked(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,7 +58,8 @@ public class NotifierController: NSWindowController, NotifierWindowDelegate {
|
||||||
messageTextField.attributedStringValue = attrString
|
messageTextField.attributedStringValue = attrString
|
||||||
let width = window?.frame.width ?? kWindowWidth
|
let width = window?.frame.width ?? kWindowWidth
|
||||||
let rect = attrString.boundingRect(
|
let rect = attrString.boundingRect(
|
||||||
with: NSSize(width: width, height: 1600), options: .usesLineFragmentOrigin)
|
with: NSSize(width: width, height: 1600), options: .usesLineFragmentOrigin
|
||||||
|
)
|
||||||
let height = rect.height
|
let height = rect.height
|
||||||
let x = messageTextField.frame.origin.x
|
let x = messageTextField.frame.origin.x
|
||||||
let y = ((window?.frame.height ?? kWindowHeight) - height) / 2
|
let y = ((window?.frame.height ?? kWindowHeight) - height) / 2
|
||||||
|
@ -66,17 +67,20 @@ public class NotifierController: NSWindowController, NotifierWindowDelegate {
|
||||||
messageTextField.frame = newFrame
|
messageTextField.frame = newFrame
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var shouldStay: Bool = false
|
private var shouldStay: Bool = false
|
||||||
private var backgroundColor: NSColor = .textBackgroundColor {
|
private var backgroundColor: NSColor = .textBackgroundColor {
|
||||||
didSet {
|
didSet {
|
||||||
window?.backgroundColor = backgroundColor
|
window?.backgroundColor = backgroundColor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var foregroundColor: NSColor = .controlTextColor {
|
private var foregroundColor: NSColor = .controlTextColor {
|
||||||
didSet {
|
didSet {
|
||||||
messageTextField.textColor = foregroundColor
|
messageTextField.textColor = foregroundColor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var waitTimer: Timer?
|
private var waitTimer: Timer?
|
||||||
private var fadeTimer: Timer?
|
private var fadeTimer: Timer?
|
||||||
|
|
||||||
|
@ -114,7 +118,8 @@ public class NotifierController: NSWindowController, NotifierWindowDelegate {
|
||||||
transparentVisualEffect.state = .active
|
transparentVisualEffect.state = .active
|
||||||
|
|
||||||
let panel = NotifierWindow(
|
let panel = NotifierWindow(
|
||||||
contentRect: windowRect, styleMask: styleMask, backing: .buffered, defer: false)
|
contentRect: windowRect, styleMask: styleMask, backing: .buffered, defer: false
|
||||||
|
)
|
||||||
panel.contentView = transparentVisualEffect
|
panel.contentView = transparentVisualEffect
|
||||||
panel.isMovableByWindowBackground = true
|
panel.isMovableByWindowBackground = true
|
||||||
panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel))
|
panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel))
|
||||||
|
@ -144,7 +149,8 @@ public class NotifierController: NSWindowController, NotifierWindowDelegate {
|
||||||
panel.clickDelegate = self
|
panel.clickDelegate = self
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
@available(*, unavailable)
|
||||||
|
required init?(coder _: NSCoder) {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,10 +188,11 @@ public class NotifierController: NSWindowController, NotifierWindowDelegate {
|
||||||
waitTimer = Timer.scheduledTimer(
|
waitTimer = Timer.scheduledTimer(
|
||||||
timeInterval: shouldStay ? 5 : 1, target: self, selector: #selector(fadeOut),
|
timeInterval: shouldStay ? 5 : 1, target: self, selector: #selector(fadeOut),
|
||||||
userInfo: nil,
|
userInfo: nil,
|
||||||
repeats: false)
|
repeats: false
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func doFadeOut(_ timer: Timer) {
|
@objc private func doFadeOut(_: Timer) {
|
||||||
let opacity = window?.alphaValue ?? 0
|
let opacity = window?.alphaValue ?? 0
|
||||||
if opacity <= 0 {
|
if opacity <= 0 {
|
||||||
close()
|
close()
|
||||||
|
@ -200,10 +207,11 @@ public class NotifierController: NSWindowController, NotifierWindowDelegate {
|
||||||
NotifierController.decreaseInstanceCount()
|
NotifierController.decreaseInstanceCount()
|
||||||
fadeTimer = Timer.scheduledTimer(
|
fadeTimer = Timer.scheduledTimer(
|
||||||
timeInterval: 0.01, target: self, selector: #selector(doFadeOut(_:)), userInfo: nil,
|
timeInterval: 0.01, target: self, selector: #selector(doFadeOut(_:)), userInfo: nil,
|
||||||
repeats: true)
|
repeats: true
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
public override func close() {
|
override public func close() {
|
||||||
waitTimer?.invalidate()
|
waitTimer?.invalidate()
|
||||||
waitTimer = nil
|
waitTimer = nil
|
||||||
fadeTimer?.invalidate()
|
fadeTimer?.invalidate()
|
||||||
|
@ -211,7 +219,7 @@ public class NotifierController: NSWindowController, NotifierWindowDelegate {
|
||||||
super.close()
|
super.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func windowDidBecomeClicked(_ window: NotifierWindow) {
|
fileprivate func windowDidBecomeClicked(_: NotifierWindow) {
|
||||||
fadeOut()
|
fadeOut()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,8 @@ class ctlPrefUI {
|
||||||
identifier: Preferences.PaneIdentifier(rawValue: "General"),
|
identifier: Preferences.PaneIdentifier(rawValue: "General"),
|
||||||
title: NSLocalizedString("General", comment: ""),
|
title: NSLocalizedString("General", comment: ""),
|
||||||
toolbarIcon: NSImage(
|
toolbarIcon: NSImage(
|
||||||
systemSymbolName: "wrench.and.screwdriver.fill", accessibilityDescription: "General Preferences")
|
systemSymbolName: "wrench.and.screwdriver.fill", accessibilityDescription: "General Preferences"
|
||||||
|
)
|
||||||
?? NSImage(named: NSImage.homeTemplateName)!
|
?? NSImage(named: NSImage.homeTemplateName)!
|
||||||
) {
|
) {
|
||||||
suiPrefPaneGeneral()
|
suiPrefPaneGeneral()
|
||||||
|
@ -41,7 +42,8 @@ class ctlPrefUI {
|
||||||
identifier: Preferences.PaneIdentifier(rawValue: "Experiences"),
|
identifier: Preferences.PaneIdentifier(rawValue: "Experiences"),
|
||||||
title: NSLocalizedString("Experience", comment: ""),
|
title: NSLocalizedString("Experience", comment: ""),
|
||||||
toolbarIcon: NSImage(
|
toolbarIcon: NSImage(
|
||||||
systemSymbolName: "person.fill.questionmark", accessibilityDescription: "Experiences Preferences")
|
systemSymbolName: "person.fill.questionmark", accessibilityDescription: "Experiences Preferences"
|
||||||
|
)
|
||||||
?? NSImage(named: NSImage.listViewTemplateName)!
|
?? NSImage(named: NSImage.listViewTemplateName)!
|
||||||
) {
|
) {
|
||||||
suiPrefPaneExperience()
|
suiPrefPaneExperience()
|
||||||
|
@ -50,7 +52,8 @@ class ctlPrefUI {
|
||||||
identifier: Preferences.PaneIdentifier(rawValue: "Dictionary"),
|
identifier: Preferences.PaneIdentifier(rawValue: "Dictionary"),
|
||||||
title: NSLocalizedString("Dictionary", comment: ""),
|
title: NSLocalizedString("Dictionary", comment: ""),
|
||||||
toolbarIcon: NSImage(
|
toolbarIcon: NSImage(
|
||||||
systemSymbolName: "character.book.closed.fill", accessibilityDescription: "Dictionary Preferences")
|
systemSymbolName: "character.book.closed.fill", accessibilityDescription: "Dictionary Preferences"
|
||||||
|
)
|
||||||
?? NSImage(named: NSImage.bookmarksTemplateName)!
|
?? NSImage(named: NSImage.bookmarksTemplateName)!
|
||||||
) {
|
) {
|
||||||
suiPrefPaneDictionary()
|
suiPrefPaneDictionary()
|
||||||
|
@ -59,7 +62,8 @@ class ctlPrefUI {
|
||||||
identifier: Preferences.PaneIdentifier(rawValue: "Keyboard"),
|
identifier: Preferences.PaneIdentifier(rawValue: "Keyboard"),
|
||||||
title: NSLocalizedString("Keyboard", comment: ""),
|
title: NSLocalizedString("Keyboard", comment: ""),
|
||||||
toolbarIcon: NSImage(
|
toolbarIcon: NSImage(
|
||||||
systemSymbolName: "keyboard.macwindow", accessibilityDescription: "Keyboard Preferences")
|
systemSymbolName: "keyboard.macwindow", accessibilityDescription: "Keyboard Preferences"
|
||||||
|
)
|
||||||
?? NSImage(named: NSImage.actionTemplateName)!
|
?? NSImage(named: NSImage.actionTemplateName)!
|
||||||
) {
|
) {
|
||||||
suiPrefPaneKeyboard()
|
suiPrefPaneKeyboard()
|
||||||
|
|
|
@ -57,7 +57,8 @@ struct suiPrefPaneDictionary: View {
|
||||||
.help(tbxUserDataPathSpecified)
|
.help(tbxUserDataPathSpecified)
|
||||||
Button {
|
Button {
|
||||||
IME.dlgOpenPath.title = NSLocalizedString(
|
IME.dlgOpenPath.title = NSLocalizedString(
|
||||||
"Choose your desired user data folder.", comment: "")
|
"Choose your desired user data folder.", comment: ""
|
||||||
|
)
|
||||||
IME.dlgOpenPath.showsResizeIndicator = true
|
IME.dlgOpenPath.showsResizeIndicator = true
|
||||||
IME.dlgOpenPath.showsHiddenFiles = true
|
IME.dlgOpenPath.showsHiddenFiles = true
|
||||||
IME.dlgOpenPath.canChooseFiles = false
|
IME.dlgOpenPath.canChooseFiles = false
|
||||||
|
@ -103,27 +104,25 @@ struct suiPrefPaneDictionary: View {
|
||||||
} label: {
|
} label: {
|
||||||
Text("↻")
|
Text("↻")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Toggle(
|
Toggle(
|
||||||
LocalizedStringKey("Automatically reload user data files if changes detected"),
|
LocalizedStringKey("Automatically reload user data files if changes detected"),
|
||||||
isOn: $selAutoReloadUserData
|
isOn: $selAutoReloadUserData
|
||||||
).controlSize(.small).onChange(of: selAutoReloadUserData) { (value) in
|
).controlSize(.small).onChange(of: selAutoReloadUserData) { value in
|
||||||
mgrPrefs.shouldAutoReloadUserDataFiles = value
|
mgrPrefs.shouldAutoReloadUserDataFiles = value
|
||||||
}
|
}
|
||||||
Divider()
|
Divider()
|
||||||
Toggle(LocalizedStringKey("Enable CNS11643 Support (2022-01-27)"), isOn: $selEnableCNS11643)
|
Toggle(LocalizedStringKey("Enable CNS11643 Support (2022-01-27)"), isOn: $selEnableCNS11643)
|
||||||
.onChange(of: selEnableCNS11643) { (value) in
|
.onChange(of: selEnableCNS11643) { value in
|
||||||
mgrPrefs.cns11643Enabled = value
|
mgrPrefs.cns11643Enabled = value
|
||||||
}
|
}
|
||||||
Toggle(
|
Toggle(
|
||||||
LocalizedStringKey("Enable symbol input support (incl. certain emoji symbols)"),
|
LocalizedStringKey("Enable symbol input support (incl. certain emoji symbols)"),
|
||||||
isOn: $selEnableSymbolInputSupport
|
isOn: $selEnableSymbolInputSupport
|
||||||
)
|
)
|
||||||
.onChange(of: selEnableSymbolInputSupport) { (value) in
|
.onChange(of: selEnableSymbolInputSupport) { value in
|
||||||
mgrPrefs.symbolInputEnabled = value
|
mgrPrefs.symbolInputEnabled = value
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,7 +63,7 @@ struct suiPrefPaneExperience: View {
|
||||||
Preferences.Section(bottomDivider: true, label: { Text(LocalizedStringKey("Selection Keys:")) }) {
|
Preferences.Section(bottomDivider: true, label: { Text(LocalizedStringKey("Selection Keys:")) }) {
|
||||||
ComboBox(items: mgrPrefs.suggestedCandidateKeys, text: $selSelectionKeys).frame(width: 180).onChange(
|
ComboBox(items: mgrPrefs.suggestedCandidateKeys, text: $selSelectionKeys).frame(width: 180).onChange(
|
||||||
of: selSelectionKeys
|
of: selSelectionKeys
|
||||||
) { (value) in
|
) { value in
|
||||||
let keys: String = (value.trimmingCharacters(in: .whitespacesAndNewlines) as String).charDeDuplicate
|
let keys: String = (value.trimmingCharacters(in: .whitespacesAndNewlines) as String).charDeDuplicate
|
||||||
do {
|
do {
|
||||||
try mgrPrefs.validate(candidateKeys: keys)
|
try mgrPrefs.validate(candidateKeys: keys)
|
||||||
|
@ -92,7 +92,7 @@ struct suiPrefPaneExperience: View {
|
||||||
Picker("", selection: $selCursorPosition) {
|
Picker("", selection: $selCursorPosition) {
|
||||||
Text(LocalizedStringKey("to the front of the phrase (like Matsushita Hanin IME)")).tag(0)
|
Text(LocalizedStringKey("to the front of the phrase (like Matsushita Hanin IME)")).tag(0)
|
||||||
Text(LocalizedStringKey("to the rear of the phrase (like MS New-Phonetic IME)")).tag(1)
|
Text(LocalizedStringKey("to the rear of the phrase (like MS New-Phonetic IME)")).tag(1)
|
||||||
}.onChange(of: selCursorPosition) { (value) in
|
}.onChange(of: selCursorPosition) { value in
|
||||||
mgrPrefs.selectPhraseAfterCursorAsCandidate = (value == 1) ? true : false
|
mgrPrefs.selectPhraseAfterCursorAsCandidate = (value == 1) ? true : false
|
||||||
}
|
}
|
||||||
.labelsHidden()
|
.labelsHidden()
|
||||||
|
@ -102,7 +102,7 @@ struct suiPrefPaneExperience: View {
|
||||||
Toggle(
|
Toggle(
|
||||||
LocalizedStringKey("Push the cursor to the front of the phrase after selection"),
|
LocalizedStringKey("Push the cursor to the front of the phrase after selection"),
|
||||||
isOn: $selPushCursorAfterSelection
|
isOn: $selPushCursorAfterSelection
|
||||||
).onChange(of: selPushCursorAfterSelection) { (value) in
|
).onChange(of: selPushCursorAfterSelection) { value in
|
||||||
mgrPrefs.moveCursorAfterSelectingCandidate = value
|
mgrPrefs.moveCursorAfterSelectingCandidate = value
|
||||||
}.controlSize(.small)
|
}.controlSize(.small)
|
||||||
}
|
}
|
||||||
|
@ -110,7 +110,7 @@ struct suiPrefPaneExperience: View {
|
||||||
Picker("", selection: $selKeyBehaviorShiftTab) {
|
Picker("", selection: $selKeyBehaviorShiftTab) {
|
||||||
Text(LocalizedStringKey("for cycling candidates")).tag(0)
|
Text(LocalizedStringKey("for cycling candidates")).tag(0)
|
||||||
Text(LocalizedStringKey("for cycling pages")).tag(1)
|
Text(LocalizedStringKey("for cycling pages")).tag(1)
|
||||||
}.onChange(of: selKeyBehaviorShiftTab) { (value) in
|
}.onChange(of: selKeyBehaviorShiftTab) { value in
|
||||||
mgrPrefs.specifyShiftTabKeyBehavior = (value == 1) ? true : false
|
mgrPrefs.specifyShiftTabKeyBehavior = (value == 1) ? true : false
|
||||||
}
|
}
|
||||||
.labelsHidden()
|
.labelsHidden()
|
||||||
|
@ -123,7 +123,7 @@ struct suiPrefPaneExperience: View {
|
||||||
Picker("", selection: $selKeyBehaviorShiftSpace) {
|
Picker("", selection: $selKeyBehaviorShiftSpace) {
|
||||||
Text(LocalizedStringKey("Space to +cycle candidates, Shift+Space to +cycle pages")).tag(0)
|
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)
|
Text(LocalizedStringKey("Space to +cycle pages, Shift+Space to +cycle candidates")).tag(1)
|
||||||
}.onChange(of: selKeyBehaviorShiftSpace) { (value) in
|
}.onChange(of: selKeyBehaviorShiftSpace) { value in
|
||||||
mgrPrefs.specifyShiftSpaceKeyBehavior = (value == 1) ? true : false
|
mgrPrefs.specifyShiftSpaceKeyBehavior = (value == 1) ? true : false
|
||||||
}
|
}
|
||||||
.labelsHidden()
|
.labelsHidden()
|
||||||
|
@ -135,20 +135,20 @@ struct suiPrefPaneExperience: View {
|
||||||
Toggle(
|
Toggle(
|
||||||
LocalizedStringKey("Enable Space key for calling candidate window"),
|
LocalizedStringKey("Enable Space key for calling candidate window"),
|
||||||
isOn: $selKeyBehaviorSpaceForCallingCandidate
|
isOn: $selKeyBehaviorSpaceForCallingCandidate
|
||||||
).onChange(of: selKeyBehaviorSpaceForCallingCandidate) { (value) in
|
).onChange(of: selKeyBehaviorSpaceForCallingCandidate) { value in
|
||||||
mgrPrefs.chooseCandidateUsingSpace = value
|
mgrPrefs.chooseCandidateUsingSpace = value
|
||||||
}
|
}
|
||||||
Toggle(
|
Toggle(
|
||||||
LocalizedStringKey("Use ESC key to clear the entire input buffer"),
|
LocalizedStringKey("Use ESC key to clear the entire input buffer"),
|
||||||
isOn: $selKeyBehaviorESCForClearingTheBuffer
|
isOn: $selKeyBehaviorESCForClearingTheBuffer
|
||||||
).onChange(of: selKeyBehaviorESCForClearingTheBuffer) { (value) in
|
).onChange(of: selKeyBehaviorESCForClearingTheBuffer) { value in
|
||||||
mgrPrefs.escToCleanInputBuffer = value
|
mgrPrefs.escToCleanInputBuffer = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Preferences.Section(label: { Text(LocalizedStringKey("Typing Style:")) }) {
|
Preferences.Section(label: { Text(LocalizedStringKey("Typing Style:")) }) {
|
||||||
Toggle(
|
Toggle(
|
||||||
LocalizedStringKey("Emulating select-candidate-per-character mode"), isOn: $selEnableSCPCTypingMode
|
LocalizedStringKey("Emulating select-candidate-per-character mode"), isOn: $selEnableSCPCTypingMode
|
||||||
).onChange(of: selEnableSCPCTypingMode) { (value) in
|
).onChange(of: selEnableSCPCTypingMode) { value in
|
||||||
mgrPrefs.useSCPCTypingMode = value
|
mgrPrefs.useSCPCTypingMode = value
|
||||||
}
|
}
|
||||||
Text(LocalizedStringKey("An accomodation for elder computer users."))
|
Text(LocalizedStringKey("An accomodation for elder computer users."))
|
||||||
|
|
|
@ -71,7 +71,7 @@ struct suiPrefPaneGeneral: View {
|
||||||
Text("32").tag(32)
|
Text("32").tag(32)
|
||||||
Text("64").tag(64)
|
Text("64").tag(64)
|
||||||
Text("96").tag(96)
|
Text("96").tag(96)
|
||||||
}.onChange(of: selCandidateUIFontSize) { (value) in
|
}.onChange(of: selCandidateUIFontSize) { value in
|
||||||
mgrPrefs.candidateListTextSize = CGFloat(value)
|
mgrPrefs.candidateListTextSize = CGFloat(value)
|
||||||
}
|
}
|
||||||
.labelsHidden()
|
.labelsHidden()
|
||||||
|
@ -86,7 +86,7 @@ struct suiPrefPaneGeneral: View {
|
||||||
Text(LocalizedStringKey("Traditional Chinese")).tag(["zh-Hant"])
|
Text(LocalizedStringKey("Traditional Chinese")).tag(["zh-Hant"])
|
||||||
Text(LocalizedStringKey("Japanese")).tag(["ja"])
|
Text(LocalizedStringKey("Japanese")).tag(["ja"])
|
||||||
Text(LocalizedStringKey("English")).tag(["en"])
|
Text(LocalizedStringKey("English")).tag(["en"])
|
||||||
}.onChange(of: selUILanguage) { (value) in
|
}.onChange(of: selUILanguage) { value in
|
||||||
IME.prtDebugIntel(value[0])
|
IME.prtDebugIntel(value[0])
|
||||||
if selUILanguage == mgrPrefs.appleLanguages
|
if selUILanguage == mgrPrefs.appleLanguages
|
||||||
|| (selUILanguage[0] == "auto"
|
|| (selUILanguage[0] == "auto"
|
||||||
|
@ -112,7 +112,7 @@ struct suiPrefPaneGeneral: View {
|
||||||
Picker("", selection: $selEnableHorizontalCandidateLayout) {
|
Picker("", selection: $selEnableHorizontalCandidateLayout) {
|
||||||
Text(LocalizedStringKey("Vertical")).tag(false)
|
Text(LocalizedStringKey("Vertical")).tag(false)
|
||||||
Text(LocalizedStringKey("Horizontal")).tag(true)
|
Text(LocalizedStringKey("Horizontal")).tag(true)
|
||||||
}.onChange(of: selEnableHorizontalCandidateLayout) { (value) in
|
}.onChange(of: selEnableHorizontalCandidateLayout) { value in
|
||||||
mgrPrefs.useHorizontalCandidateList = value
|
mgrPrefs.useHorizontalCandidateList = value
|
||||||
}
|
}
|
||||||
.labelsHidden()
|
.labelsHidden()
|
||||||
|
@ -128,31 +128,31 @@ struct suiPrefPaneGeneral: View {
|
||||||
Toggle(
|
Toggle(
|
||||||
LocalizedStringKey("Auto-convert traditional Chinese glyphs to KangXi characters"),
|
LocalizedStringKey("Auto-convert traditional Chinese glyphs to KangXi characters"),
|
||||||
isOn: $selEnableKanjiConvToKangXi
|
isOn: $selEnableKanjiConvToKangXi
|
||||||
).onChange(of: selEnableKanjiConvToKangXi) { (value) in
|
).onChange(of: selEnableKanjiConvToKangXi) { value in
|
||||||
mgrPrefs.chineseConversionEnabled = value
|
mgrPrefs.chineseConversionEnabled = value
|
||||||
}
|
}
|
||||||
Toggle(
|
Toggle(
|
||||||
LocalizedStringKey("Auto-convert traditional Chinese glyphs to JIS Shinjitai characters"),
|
LocalizedStringKey("Auto-convert traditional Chinese glyphs to JIS Shinjitai characters"),
|
||||||
isOn: $selEnableKanjiConvToJIS
|
isOn: $selEnableKanjiConvToJIS
|
||||||
).onChange(of: selEnableKanjiConvToJIS) { (value) in
|
).onChange(of: selEnableKanjiConvToJIS) { value in
|
||||||
mgrPrefs.shiftJISShinjitaiOutputEnabled = value
|
mgrPrefs.shiftJISShinjitaiOutputEnabled = value
|
||||||
}
|
}
|
||||||
Toggle(
|
Toggle(
|
||||||
LocalizedStringKey("Stop farting (when typed phonetic combination is invalid, etc.)"),
|
LocalizedStringKey("Stop farting (when typed phonetic combination is invalid, etc.)"),
|
||||||
isOn: $selEnableFartSuppressor
|
isOn: $selEnableFartSuppressor
|
||||||
).onChange(of: selEnableFartSuppressor) { (value) in
|
).onChange(of: selEnableFartSuppressor) { value in
|
||||||
mgrPrefs.shouldNotFartInLieuOfBeep = value
|
mgrPrefs.shouldNotFartInLieuOfBeep = value
|
||||||
clsSFX.beep()
|
clsSFX.beep()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Preferences.Section(label: { Text(LocalizedStringKey("Misc Settings:")).controlSize(.small) }) {
|
Preferences.Section(label: { Text(LocalizedStringKey("Misc Settings:")).controlSize(.small) }) {
|
||||||
Toggle(LocalizedStringKey("Check for updates automatically"), isOn: $selEnableAutoUpdateCheck)
|
Toggle(LocalizedStringKey("Check for updates automatically"), isOn: $selEnableAutoUpdateCheck)
|
||||||
.onChange(of: selEnableAutoUpdateCheck) { (value) in
|
.onChange(of: selEnableAutoUpdateCheck) { value in
|
||||||
mgrPrefs.checkUpdateAutomatically = value
|
mgrPrefs.checkUpdateAutomatically = value
|
||||||
}
|
}
|
||||||
.controlSize(.small)
|
.controlSize(.small)
|
||||||
Toggle(LocalizedStringKey("Debug Mode"), isOn: $selEnableDebugMode).controlSize(.small)
|
Toggle(LocalizedStringKey("Debug Mode"), isOn: $selEnableDebugMode).controlSize(.small)
|
||||||
.onChange(of: selEnableDebugMode) { (value) in
|
.onChange(of: selEnableDebugMode) { value in
|
||||||
mgrPrefs.isDebugModeEnabled = value
|
mgrPrefs.isDebugModeEnabled = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,7 +54,7 @@ struct suiPrefPaneKeyboard: View {
|
||||||
Text(LocalizedStringKey("MiTAC")).tag(5)
|
Text(LocalizedStringKey("MiTAC")).tag(5)
|
||||||
Text(LocalizedStringKey("Fake Seigyou")).tag(6)
|
Text(LocalizedStringKey("Fake Seigyou")).tag(6)
|
||||||
Text(LocalizedStringKey("Hanyu Pinyin with Numeral Intonation")).tag(10)
|
Text(LocalizedStringKey("Hanyu Pinyin with Numeral Intonation")).tag(10)
|
||||||
}.onChange(of: selMandarinParser) { (value) in
|
}.onChange(of: selMandarinParser) { value in
|
||||||
mgrPrefs.mandarinParser = value
|
mgrPrefs.mandarinParser = value
|
||||||
}
|
}
|
||||||
.labelsHidden()
|
.labelsHidden()
|
||||||
|
@ -69,7 +69,7 @@ struct suiPrefPaneKeyboard: View {
|
||||||
Text(IME.arrEnumerateSystemKeyboardLayouts[id].strName).tag(
|
Text(IME.arrEnumerateSystemKeyboardLayouts[id].strName).tag(
|
||||||
IME.arrEnumerateSystemKeyboardLayouts[id].strValue)
|
IME.arrEnumerateSystemKeyboardLayouts[id].strValue)
|
||||||
}.id(UUID())
|
}.id(UUID())
|
||||||
}.onChange(of: selBasicKeyboardLayout) { (value) in
|
}.onChange(of: selBasicKeyboardLayout) { value in
|
||||||
mgrPrefs.basicKeyboardLayout = value
|
mgrPrefs.basicKeyboardLayout = value
|
||||||
}
|
}
|
||||||
.labelsHidden()
|
.labelsHidden()
|
||||||
|
|
|
@ -41,7 +41,8 @@ public class TooltipController: NSWindowController {
|
||||||
let contentRect = NSRect(x: 128.0, y: 128.0, width: 300.0, height: 20.0)
|
let contentRect = NSRect(x: 128.0, y: 128.0, width: 300.0, height: 20.0)
|
||||||
let styleMask: NSWindow.StyleMask = [.borderless, .nonactivatingPanel]
|
let styleMask: NSWindow.StyleMask = [.borderless, .nonactivatingPanel]
|
||||||
let panel = NSPanel(
|
let panel = NSPanel(
|
||||||
contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false)
|
contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false
|
||||||
|
)
|
||||||
panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel) + 1)
|
panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel) + 1)
|
||||||
panel.hasShadow = true
|
panel.hasShadow = true
|
||||||
|
|
||||||
|
@ -58,7 +59,8 @@ public class TooltipController: NSWindowController {
|
||||||
super.init(window: panel)
|
super.init(window: panel)
|
||||||
}
|
}
|
||||||
|
|
||||||
public required init?(coder: NSCoder) {
|
@available(*, unavailable)
|
||||||
|
public required init?(coder _: NSCoder) {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,15 +79,14 @@ public class TooltipController: NSWindowController {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func set(windowLocation windowTopLeftPoint: NSPoint) {
|
private func set(windowLocation windowTopLeftPoint: NSPoint) {
|
||||||
|
|
||||||
var adjustedPoint = windowTopLeftPoint
|
var adjustedPoint = windowTopLeftPoint
|
||||||
adjustedPoint.y -= 5
|
adjustedPoint.y -= 5
|
||||||
|
|
||||||
var screenFrame = NSScreen.main?.visibleFrame ?? NSRect.zero
|
var screenFrame = NSScreen.main?.visibleFrame ?? NSRect.zero
|
||||||
for screen in NSScreen.screens {
|
for screen in NSScreen.screens {
|
||||||
let frame = screen.visibleFrame
|
let frame = screen.visibleFrame
|
||||||
if windowTopLeftPoint.x >= frame.minX && windowTopLeftPoint.x <= frame.maxX
|
if windowTopLeftPoint.x >= frame.minX, windowTopLeftPoint.x <= frame.maxX,
|
||||||
&& windowTopLeftPoint.y >= frame.minY && windowTopLeftPoint.y <= frame.maxY
|
windowTopLeftPoint.y >= frame.minY, windowTopLeftPoint.y <= frame.maxY
|
||||||
{
|
{
|
||||||
screenFrame = frame
|
screenFrame = frame
|
||||||
break
|
break
|
||||||
|
@ -115,16 +116,15 @@ public class TooltipController: NSWindowController {
|
||||||
}
|
}
|
||||||
|
|
||||||
window?.setFrameTopLeftPoint(adjustedPoint)
|
window?.setFrameTopLeftPoint(adjustedPoint)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func adjustSize() {
|
private func adjustSize() {
|
||||||
let attrString = messageTextField.attributedStringValue
|
let attrString = messageTextField.attributedStringValue
|
||||||
var rect = attrString.boundingRect(
|
var rect = attrString.boundingRect(
|
||||||
with: NSSize(width: 1600.0, height: 1600.0), options: .usesLineFragmentOrigin)
|
with: NSSize(width: 1600.0, height: 1600.0), options: .usesLineFragmentOrigin
|
||||||
|
)
|
||||||
rect.size.width += 10
|
rect.size.width += 10
|
||||||
messageTextField.frame = rect
|
messageTextField.frame = rect
|
||||||
window?.setFrame(rect, display: true)
|
window?.setFrame(rect, display: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,8 +27,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
|
||||||
@objc(AboutWindow) class ctlAboutWindow: NSWindowController {
|
@objc(AboutWindow) class ctlAboutWindow: NSWindowController {
|
||||||
@IBOutlet weak var appVersionLabel: NSTextField!
|
@IBOutlet var appVersionLabel: NSTextField!
|
||||||
@IBOutlet weak var appCopyrightLabel: NSTextField!
|
@IBOutlet var appCopyrightLabel: NSTextField!
|
||||||
@IBOutlet var appEULAContent: NSTextView!
|
@IBOutlet var appEULAContent: NSTextView!
|
||||||
|
|
||||||
override func windowDidLoad() {
|
override func windowDidLoad() {
|
||||||
|
@ -53,10 +53,11 @@ import Cocoa
|
||||||
appEULAContent.string = eulaContent
|
appEULAContent.string = eulaContent
|
||||||
}
|
}
|
||||||
appVersionLabel.stringValue = String(
|
appVersionLabel.stringValue = String(
|
||||||
format: "%@ Build %@", versionString, installingVersion)
|
format: "%@ Build %@", versionString, installingVersion
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func btnWiki(_ sender: NSButton) {
|
@IBAction func btnWiki(_: NSButton) {
|
||||||
if let url = URL(string: "https://gitee.com/vchewing/vChewing-macOS/wikis") {
|
if let url = URL(string: "https://gitee.com/vchewing/vChewing-macOS/wikis") {
|
||||||
NSWorkspace.shared.open(url)
|
NSWorkspace.shared.open(url)
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,10 +35,10 @@ class ctlNonModalAlertWindow: NSWindowController {
|
||||||
@objc(sharedInstance)
|
@objc(sharedInstance)
|
||||||
static let shared = ctlNonModalAlertWindow(windowNibName: "frmNonModalAlertWindow")
|
static let shared = ctlNonModalAlertWindow(windowNibName: "frmNonModalAlertWindow")
|
||||||
|
|
||||||
@IBOutlet weak var titleTextField: NSTextField!
|
@IBOutlet var titleTextField: NSTextField!
|
||||||
@IBOutlet weak var contentTextField: NSTextField!
|
@IBOutlet var contentTextField: NSTextField!
|
||||||
@IBOutlet weak var confirmButton: NSButton!
|
@IBOutlet var confirmButton: NSButton!
|
||||||
@IBOutlet weak var cancelButton: NSButton!
|
@IBOutlet var cancelButton: NSButton!
|
||||||
weak var delegate: ctlNonModalAlertWindowDelegate?
|
weak var delegate: ctlNonModalAlertWindowDelegate?
|
||||||
|
|
||||||
@objc func show(
|
@objc func show(
|
||||||
|
@ -96,7 +96,8 @@ class ctlNonModalAlertWindow: NSWindowController {
|
||||||
infiniteHeightFrame.size.height = 10240
|
infiniteHeightFrame.size.height = 10240
|
||||||
newFrame = (content as NSString).boundingRect(
|
newFrame = (content as NSString).boundingRect(
|
||||||
with: infiniteHeightFrame.size, options: [.usesLineFragmentOrigin],
|
with: infiniteHeightFrame.size, options: [.usesLineFragmentOrigin],
|
||||||
attributes: [.font: contentTextField.font!])
|
attributes: [.font: contentTextField.font!]
|
||||||
|
)
|
||||||
newFrame.size.width = max(newFrame.size.width, oldFrame.size.width)
|
newFrame.size.width = max(newFrame.size.width, oldFrame.size.width)
|
||||||
newFrame.size.height += 4.0
|
newFrame.size.height += 4.0
|
||||||
newFrame.origin = oldFrame.origin
|
newFrame.origin = oldFrame.origin
|
||||||
|
@ -112,7 +113,7 @@ class ctlNonModalAlertWindow: NSWindowController {
|
||||||
NSApp.activate(ignoringOtherApps: true)
|
NSApp.activate(ignoringOtherApps: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func confirmButtonAction(_ sender: Any) {
|
@IBAction func confirmButtonAction(_: Any) {
|
||||||
delegate?.ctlNonModalAlertWindowDidConfirm(self)
|
delegate?.ctlNonModalAlertWindowDidConfirm(self)
|
||||||
window?.orderOut(self)
|
window?.orderOut(self)
|
||||||
}
|
}
|
||||||
|
@ -121,10 +122,9 @@ class ctlNonModalAlertWindow: NSWindowController {
|
||||||
cancel(sender)
|
cancel(sender)
|
||||||
}
|
}
|
||||||
|
|
||||||
func cancel(_ sender: Any) {
|
func cancel(_: Any) {
|
||||||
delegate?.ctlNonModalAlertWindowDidCancel(self)
|
delegate?.ctlNonModalAlertWindowDidCancel(self)
|
||||||
delegate = nil
|
delegate = nil
|
||||||
window?.orderOut(self)
|
window?.orderOut(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,13 +31,13 @@ import Cocoa
|
||||||
// in Objective-C in order to let IMK to see the same class name as
|
// in Objective-C in order to let IMK to see the same class name as
|
||||||
// the "InputMethodServerPreferencesWindowControllerClass" in Info.plist.
|
// the "InputMethodServerPreferencesWindowControllerClass" in Info.plist.
|
||||||
@objc(ctlPrefWindow) class ctlPrefWindow: NSWindowController {
|
@objc(ctlPrefWindow) class ctlPrefWindow: NSWindowController {
|
||||||
@IBOutlet weak var fontSizePopUpButton: NSPopUpButton!
|
@IBOutlet var fontSizePopUpButton: NSPopUpButton!
|
||||||
@IBOutlet weak var uiLanguageButton: NSPopUpButton!
|
@IBOutlet var uiLanguageButton: NSPopUpButton!
|
||||||
@IBOutlet weak var basicKeyboardLayoutButton: NSPopUpButton!
|
@IBOutlet var basicKeyboardLayoutButton: NSPopUpButton!
|
||||||
@IBOutlet weak var selectionKeyComboBox: NSComboBox!
|
@IBOutlet var selectionKeyComboBox: NSComboBox!
|
||||||
@IBOutlet weak var chkTrad2KangXi: NSButton!
|
@IBOutlet var chkTrad2KangXi: NSButton!
|
||||||
@IBOutlet weak var chkTrad2JISShinjitai: NSButton!
|
@IBOutlet var chkTrad2JISShinjitai: NSButton!
|
||||||
@IBOutlet weak var lblCurrentlySpecifiedUserDataFolder: NSTextFieldCell!
|
@IBOutlet var lblCurrentlySpecifiedUserDataFolder: NSTextFieldCell!
|
||||||
|
|
||||||
var currentLanguageSelectItem: NSMenuItem?
|
var currentLanguageSelectItem: NSMenuItem?
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ import Cocoa
|
||||||
let appleLanguages = mgrPrefs.appleLanguages
|
let appleLanguages = mgrPrefs.appleLanguages
|
||||||
for language in languages {
|
for language in languages {
|
||||||
let menuItem = NSMenuItem()
|
let menuItem = NSMenuItem()
|
||||||
menuItem.title = NSLocalizedString(language, comment: "")
|
menuItem.title = NSLocalizedString(language, comment: language)
|
||||||
menuItem.representedObject = language
|
menuItem.representedObject = language
|
||||||
|
|
||||||
if language == "auto" {
|
if language == "auto" {
|
||||||
|
@ -105,8 +105,8 @@ import Cocoa
|
||||||
}
|
}
|
||||||
|
|
||||||
if let asciiCapablePtr = TISGetInputSourceProperty(
|
if let asciiCapablePtr = TISGetInputSourceProperty(
|
||||||
source, kTISPropertyInputSourceIsASCIICapable)
|
source, kTISPropertyInputSourceIsASCIICapable
|
||||||
{
|
) {
|
||||||
let asciiCapable = Unmanaged<CFBoolean>.fromOpaque(asciiCapablePtr)
|
let asciiCapable = Unmanaged<CFBoolean>.fromOpaque(asciiCapablePtr)
|
||||||
.takeUnretainedValue()
|
.takeUnretainedValue()
|
||||||
if asciiCapable != kCFBooleanTrue {
|
if asciiCapable != kCFBooleanTrue {
|
||||||
|
@ -175,33 +175,33 @@ import Cocoa
|
||||||
|
|
||||||
// 這裡有必要加上這段處理,用來確保藉由偏好設定介面動過的 CNS 開關能夠立刻生效。
|
// 這裡有必要加上這段處理,用來確保藉由偏好設定介面動過的 CNS 開關能夠立刻生效。
|
||||||
// 所有涉及到語言模型開關的內容均需要這樣處理。
|
// 所有涉及到語言模型開關的內容均需要這樣處理。
|
||||||
@IBAction func toggleCNSSupport(_ sender: Any) {
|
@IBAction func toggleCNSSupport(_: Any) {
|
||||||
mgrLangModel.setCNSEnabled(mgrPrefs.cns11643Enabled)
|
mgrLangModel.setCNSEnabled(mgrPrefs.cns11643Enabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func toggleSymbolInputEnabled(_ sender: Any) {
|
@IBAction func toggleSymbolInputEnabled(_: Any) {
|
||||||
mgrLangModel.setSymbolEnabled(mgrPrefs.symbolInputEnabled)
|
mgrLangModel.setSymbolEnabled(mgrPrefs.symbolInputEnabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func toggleTrad2KangXiAction(_ sender: Any) {
|
@IBAction func toggleTrad2KangXiAction(_: Any) {
|
||||||
if chkTrad2KangXi.state == .on && chkTrad2JISShinjitai.state == .on {
|
if chkTrad2KangXi.state == .on, chkTrad2JISShinjitai.state == .on {
|
||||||
mgrPrefs.toggleShiftJISShinjitaiOutputEnabled()
|
mgrPrefs.toggleShiftJISShinjitaiOutputEnabled()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func toggleTrad2JISShinjitaiAction(_ sender: Any) {
|
@IBAction func toggleTrad2JISShinjitaiAction(_: Any) {
|
||||||
if chkTrad2KangXi.state == .on && chkTrad2JISShinjitai.state == .on {
|
if chkTrad2KangXi.state == .on, chkTrad2JISShinjitai.state == .on {
|
||||||
mgrPrefs.toggleChineseConversionEnabled()
|
mgrPrefs.toggleChineseConversionEnabled()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func updateBasicKeyboardLayoutAction(_ sender: Any) {
|
@IBAction func updateBasicKeyboardLayoutAction(_: Any) {
|
||||||
if let sourceID = basicKeyboardLayoutButton.selectedItem?.representedObject as? String {
|
if let sourceID = basicKeyboardLayoutButton.selectedItem?.representedObject as? String {
|
||||||
mgrPrefs.basicKeyboardLayout = sourceID
|
mgrPrefs.basicKeyboardLayout = sourceID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func updateUiLanguageAction(_ sender: Any) {
|
@IBAction func updateUiLanguageAction(_: Any) {
|
||||||
if let selectItem = uiLanguageButton.selectedItem {
|
if let selectItem = uiLanguageButton.selectedItem {
|
||||||
if currentLanguageSelectItem == selectItem {
|
if currentLanguageSelectItem == selectItem {
|
||||||
return
|
return
|
||||||
|
@ -219,7 +219,7 @@ import Cocoa
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func clickedWhetherIMEShouldNotFartToggleAction(_ sender: Any) {
|
@IBAction func clickedWhetherIMEShouldNotFartToggleAction(_: Any) {
|
||||||
clsSFX.beep()
|
clsSFX.beep()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -249,13 +249,14 @@ import Cocoa
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func resetSpecifiedUserDataFolder(_ sender: Any) {
|
@IBAction func resetSpecifiedUserDataFolder(_: Any) {
|
||||||
mgrPrefs.resetSpecifiedUserDataFolder()
|
mgrPrefs.resetSpecifiedUserDataFolder()
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func chooseUserDataFolderToSpecify(_ sender: Any) {
|
@IBAction func chooseUserDataFolderToSpecify(_: Any) {
|
||||||
IME.dlgOpenPath.title = NSLocalizedString(
|
IME.dlgOpenPath.title = NSLocalizedString(
|
||||||
"Choose your desired user data folder.", comment: "")
|
"Choose your desired user data folder.", comment: ""
|
||||||
|
)
|
||||||
IME.dlgOpenPath.showsResizeIndicator = true
|
IME.dlgOpenPath.showsResizeIndicator = true
|
||||||
IME.dlgOpenPath.showsHiddenFiles = true
|
IME.dlgOpenPath.showsHiddenFiles = true
|
||||||
IME.dlgOpenPath.canChooseFiles = false
|
IME.dlgOpenPath.canChooseFiles = false
|
||||||
|
@ -292,5 +293,4 @@ import Cocoa
|
||||||
}
|
}
|
||||||
} // End If self.window != nil
|
} // End If self.window != nil
|
||||||
} // End IBAction
|
} // End IBAction
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,20 +26,20 @@ import Cocoa
|
||||||
|
|
||||||
@NSApplicationMain
|
@NSApplicationMain
|
||||||
class AppDelegate: NSObject, NSApplicationDelegate {
|
class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
|
|
||||||
private var ctlAboutWindowInstance: ctlAboutWindow? // New About Window
|
private var ctlAboutWindowInstance: ctlAboutWindow? // New About Window
|
||||||
|
|
||||||
func applicationDidFinishLaunching(_ aNotification: Notification) {
|
func applicationDidFinishLaunching(_: Notification) {
|
||||||
// Insert code here to initialize your application
|
// Insert code here to initialize your application
|
||||||
}
|
}
|
||||||
|
|
||||||
func applicationWillTerminate(_ aNotification: Notification) {
|
func applicationWillTerminate(_: Notification) {
|
||||||
// Insert code here to tear down your application
|
// Insert code here to tear down your application
|
||||||
}
|
}
|
||||||
|
|
||||||
func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {
|
func applicationShouldTerminate(_: NSApplication) -> NSApplication.TerminateReply {
|
||||||
.terminateNow
|
.terminateNow
|
||||||
}
|
}
|
||||||
|
|
||||||
// New About Window
|
// New About Window
|
||||||
@objc func showAbout() {
|
@objc func showAbout() {
|
||||||
if ctlAboutWindowInstance == nil {
|
if ctlAboutWindowInstance == nil {
|
||||||
|
@ -49,8 +49,9 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
ctlAboutWindowInstance?.window?.orderFrontRegardless() // 逼著關於視窗往最前方顯示
|
ctlAboutWindowInstance?.window?.orderFrontRegardless() // 逼著關於視窗往最前方顯示
|
||||||
ctlAboutWindowInstance?.window?.level = .statusBar
|
ctlAboutWindowInstance?.window?.level = .statusBar
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call the New About Window
|
// Call the New About Window
|
||||||
@IBAction func about(_ sender: Any) {
|
@IBAction func about(_: Any) {
|
||||||
(NSApp.delegate as? AppDelegate)?.showAbout()
|
(NSApp.delegate as? AppDelegate)?.showAbout()
|
||||||
NSApplication.shared.activate(ignoringOtherApps: true)
|
NSApplication.shared.activate(ignoringOtherApps: true)
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,11 +31,9 @@ class Content: NSObject {
|
||||||
public init(contentString: String) {
|
public init(contentString: String) {
|
||||||
self.contentString = contentString
|
self.contentString = contentString
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Content {
|
extension Content {
|
||||||
|
|
||||||
func read(from data: Data) {
|
func read(from data: Data) {
|
||||||
contentString = String(bytes: data, encoding: .utf8)!
|
contentString = String(bytes: data, encoding: .utf8)!
|
||||||
}
|
}
|
||||||
|
@ -43,5 +41,4 @@ extension Content {
|
||||||
func data() -> Data? {
|
func data() -> Data? {
|
||||||
contentString.data(using: .utf8)
|
contentString.data(using: .utf8)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,6 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
|
||||||
class Document: NSDocument {
|
class Document: NSDocument {
|
||||||
|
|
||||||
@objc var content = Content(contentString: "")
|
@objc var content = Content(contentString: "")
|
||||||
var contentViewController: ViewController!
|
var contentViewController: ViewController!
|
||||||
|
|
||||||
|
@ -43,7 +42,7 @@ class Document: NSDocument {
|
||||||
|
|
||||||
// This enables asynchronous-writing.
|
// This enables asynchronous-writing.
|
||||||
override func canAsynchronouslyWrite(
|
override func canAsynchronouslyWrite(
|
||||||
to url: URL, ofType typeName: String, for saveOperation: NSDocument.SaveOperationType
|
to _: URL, ofType _: String, for _: NSDocument.SaveOperationType
|
||||||
) -> Bool {
|
) -> Bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
@ -77,7 +76,7 @@ class Document: NSDocument {
|
||||||
// MARK: - Reading and Writing
|
// MARK: - Reading and Writing
|
||||||
|
|
||||||
/// - Tag: readExample
|
/// - Tag: readExample
|
||||||
override func read(from data: Data, ofType typeName: String) throws {
|
override func read(from data: Data, ofType _: String) throws {
|
||||||
var strToDealWith = String(decoding: data, as: UTF8.self)
|
var strToDealWith = String(decoding: data, as: UTF8.self)
|
||||||
strToDealWith.formatConsolidate(cnvHYPYtoBPMF: false)
|
strToDealWith.formatConsolidate(cnvHYPYtoBPMF: false)
|
||||||
let processedIncomingData = Data(strToDealWith.utf8)
|
let processedIncomingData = Data(strToDealWith.utf8)
|
||||||
|
@ -85,7 +84,7 @@ class Document: NSDocument {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// - Tag: writeExample
|
/// - Tag: writeExample
|
||||||
override func data(ofType typeName: String) throws -> Data {
|
override func data(ofType _: String) throws -> Data {
|
||||||
var strToDealWith = content.contentString
|
var strToDealWith = content.contentString
|
||||||
strToDealWith.formatConsolidate(cnvHYPYtoBPMF: true)
|
strToDealWith.formatConsolidate(cnvHYPYtoBPMF: true)
|
||||||
let outputData = Data(strToDealWith.utf8)
|
let outputData = Data(strToDealWith.utf8)
|
||||||
|
@ -108,24 +107,26 @@ class Document: NSDocument {
|
||||||
|
|
||||||
printInfo.dictionary().setObject(
|
printInfo.dictionary().setObject(
|
||||||
NSNumber(value: true),
|
NSNumber(value: true),
|
||||||
forKey: NSPrintInfo.AttributeKey.headerAndFooter as NSCopying)
|
forKey: NSPrintInfo.AttributeKey.headerAndFooter as NSCopying
|
||||||
|
)
|
||||||
|
|
||||||
return thePrintInfo
|
return thePrintInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
func printOperationDidRun(
|
func printOperationDidRun(
|
||||||
_ printOperation: NSPrintOperation, success: Bool, contextInfo: UnsafeMutableRawPointer?
|
_: NSPrintOperation, success _: Bool, contextInfo _: UnsafeMutableRawPointer?
|
||||||
) {
|
) {
|
||||||
// Printing finished...
|
// Printing finished...
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction override func printDocument(_ sender: Any?) {
|
@IBAction override func printDocument(_: Any?) {
|
||||||
// Print the NSTextView.
|
// Print the NSTextView.
|
||||||
|
|
||||||
// Create a copy to manipulate for printing.
|
// Create a copy to manipulate for printing.
|
||||||
let pageSize = NSSize(
|
let pageSize = NSSize(
|
||||||
width: (printInfo.paperSize.width), height: (printInfo.paperSize.height))
|
width: printInfo.paperSize.width, height: printInfo.paperSize.height
|
||||||
|
)
|
||||||
let textView = NSTextView(
|
let textView = NSTextView(
|
||||||
frame: NSRect(x: 0.0, y: 0.0, width: pageSize.width, height: pageSize.height))
|
frame: NSRect(x: 0.0, y: 0.0, width: pageSize.width, height: pageSize.height))
|
||||||
|
|
||||||
|
@ -139,7 +140,7 @@ class Document: NSDocument {
|
||||||
printOperation.runModal(
|
printOperation.runModal(
|
||||||
for: windowControllers[0].window!,
|
for: windowControllers[0].window!,
|
||||||
delegate: self,
|
delegate: self,
|
||||||
didRun: #selector(printOperationDidRun(_:success:contextInfo:)), contextInfo: nil)
|
didRun: #selector(printOperationDidRun(_:success:contextInfo:)), contextInfo: nil
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,15 +29,19 @@ extension String {
|
||||||
// Ref: https://stackoverflow.com/a/40993403/4162914 && https://stackoverflow.com/a/71291137/4162914
|
// Ref: https://stackoverflow.com/a/40993403/4162914 && https://stackoverflow.com/a/71291137/4162914
|
||||||
do {
|
do {
|
||||||
let regex = try NSRegularExpression(
|
let regex = try NSRegularExpression(
|
||||||
pattern: pattern, options: [.caseInsensitive, .anchorsMatchLines])
|
pattern: pattern, options: [.caseInsensitive, .anchorsMatchLines]
|
||||||
|
)
|
||||||
let range = NSRange(startIndex..., in: self)
|
let range = NSRange(startIndex..., in: self)
|
||||||
self = regex.stringByReplacingMatches(
|
self = regex.stringByReplacingMatches(
|
||||||
in: self, options: [], range: range, withTemplate: replaceWith)
|
in: self, options: [], range: range, withTemplate: replaceWith
|
||||||
|
)
|
||||||
} catch { return }
|
} catch { return }
|
||||||
}
|
}
|
||||||
|
|
||||||
mutating func selfReplace(_ strOf: String, _ strWith: String = "") {
|
mutating func selfReplace(_ strOf: String, _ strWith: String = "") {
|
||||||
self = self.replacingOccurrences(of: strOf, with: strWith)
|
self = replacingOccurrences(of: strOf, with: strWith)
|
||||||
}
|
}
|
||||||
|
|
||||||
mutating func formatConsolidate(cnvHYPYtoBPMF: Bool) {
|
mutating func formatConsolidate(cnvHYPYtoBPMF: Bool) {
|
||||||
// Step 1: Consolidating formats per line.
|
// Step 1: Consolidating formats per line.
|
||||||
var strProcessed = self
|
var strProcessed = self
|
||||||
|
|
|
@ -25,7 +25,6 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
|
||||||
class ViewController: NSViewController, NSTextViewDelegate {
|
class ViewController: NSViewController, NSTextViewDelegate {
|
||||||
|
|
||||||
/// - Tag: setRepresentedObjectExample
|
/// - Tag: setRepresentedObjectExample
|
||||||
override var representedObject: Any? {
|
override var representedObject: Any? {
|
||||||
didSet {
|
didSet {
|
||||||
|
@ -54,12 +53,11 @@ class ViewController: NSViewController, NSTextViewDelegate {
|
||||||
|
|
||||||
// MARK: - NSTextViewDelegate
|
// MARK: - NSTextViewDelegate
|
||||||
|
|
||||||
func textDidBeginEditing(_ notification: Notification) {
|
func textDidBeginEditing(_: Notification) {
|
||||||
document?.objectDidBeginEditing(self)
|
document?.objectDidBeginEditing(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
func textDidEndEditing(_ notification: Notification) {
|
func textDidEndEditing(_: Notification) {
|
||||||
document?.objectDidEndEditing(self)
|
document?.objectDidEndEditing(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,6 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
|
||||||
class WindowController: NSWindowController, NSWindowDelegate {
|
class WindowController: NSWindowController, NSWindowDelegate {
|
||||||
|
|
||||||
override func windowDidLoad() {
|
override func windowDidLoad() {
|
||||||
super.windowDidLoad()
|
super.windowDidLoad()
|
||||||
}
|
}
|
||||||
|
@ -37,5 +36,4 @@ class WindowController: NSWindowController, NSWindowDelegate {
|
||||||
*/
|
*/
|
||||||
shouldCascadeWindows = true
|
shouldCascadeWindows = true
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,8 +27,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
|
||||||
@objc(AboutWindow) class ctlAboutWindow: NSWindowController {
|
@objc(AboutWindow) class ctlAboutWindow: NSWindowController {
|
||||||
@IBOutlet weak var appVersionLabel: NSTextField!
|
@IBOutlet var appVersionLabel: NSTextField!
|
||||||
@IBOutlet weak var appCopyrightLabel: NSTextField!
|
@IBOutlet var appCopyrightLabel: NSTextField!
|
||||||
@IBOutlet var appEULAContent: NSTextView!
|
@IBOutlet var appEULAContent: NSTextView!
|
||||||
|
|
||||||
override func windowDidLoad() {
|
override func windowDidLoad() {
|
||||||
|
@ -53,10 +53,11 @@ import Cocoa
|
||||||
appEULAContent.string = eulaContent
|
appEULAContent.string = eulaContent
|
||||||
}
|
}
|
||||||
appVersionLabel.stringValue = String(
|
appVersionLabel.stringValue = String(
|
||||||
format: "%@ Build %@", versionString, installingVersion)
|
format: "%@ Build %@", versionString, installingVersion
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func btnWiki(_ sender: NSButton) {
|
@IBAction func btnWiki(_: NSButton) {
|
||||||
if let url = URL(string: "https://gitee.com/vchewing/vChewing-macOS/wikis") {
|
if let url = URL(string: "https://gitee.com/vchewing/vChewing-macOS/wikis") {
|
||||||
NSWorkspace.shared.open(url)
|
NSWorkspace.shared.open(url)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue