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