InputHandler // Implementing Code Point Input support.
This commit is contained in:
parent
5245585b60
commit
8734ebd68f
|
@ -108,6 +108,29 @@ public class InputHandler: InputHandlerProtocol {
|
||||||
public func clear() {
|
public func clear() {
|
||||||
clearComposerAndCalligrapher()
|
clearComposerAndCalligrapher()
|
||||||
compositor.clear()
|
compositor.clear()
|
||||||
|
isCodePointInputMode = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Codepoint Input Buffer.
|
||||||
|
|
||||||
|
var isCodePointInputMode = false {
|
||||||
|
willSet {
|
||||||
|
strCodePointBuffer.removeAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var strCodePointBuffer = ""
|
||||||
|
|
||||||
|
var tooltipCodePointInputMode: String {
|
||||||
|
let commonTerm = NSMutableString()
|
||||||
|
commonTerm.insert("Code Point Input Mode.".localized, at: 0)
|
||||||
|
switch IMEApp.currentInputMode {
|
||||||
|
case .imeModeCHS: commonTerm.insert("[GB] ", at: 0)
|
||||||
|
case .imeModeCHT: commonTerm.insert("[Big5] ", at: 0)
|
||||||
|
default: break
|
||||||
|
}
|
||||||
|
commonTerm.append(" ")
|
||||||
|
return commonTerm.description
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Functions dealing with Megrez.
|
// MARK: - Functions dealing with Megrez.
|
||||||
|
@ -366,7 +389,10 @@ public class InputHandler: InputHandlerProtocol {
|
||||||
|
|
||||||
// MARK: - Extracted methods and functions (Tekkon).
|
// MARK: - Extracted methods and functions (Tekkon).
|
||||||
|
|
||||||
var isComposerOrCalligrapherEmpty: Bool { prefs.cassetteEnabled ? calligrapher.isEmpty : composer.isEmpty }
|
var isComposerOrCalligrapherEmpty: Bool {
|
||||||
|
if !strCodePointBuffer.isEmpty { return false }
|
||||||
|
return prefs.cassetteEnabled ? calligrapher.isEmpty : composer.isEmpty
|
||||||
|
}
|
||||||
|
|
||||||
/// 獲取與當前注音排列或拼音輸入種類有關的標點索引鍵,以英數下畫線「_」結尾。
|
/// 獲取與當前注音排列或拼音輸入種類有關的標點索引鍵,以英數下畫線「_」結尾。
|
||||||
var currentKeyboardParser: String { currentKeyboardParserType.name + "_" }
|
var currentKeyboardParser: String { currentKeyboardParserType.name + "_" }
|
||||||
|
@ -400,6 +426,7 @@ public class InputHandler: InputHandlerProtocol {
|
||||||
public func clearComposerAndCalligrapher() {
|
public func clearComposerAndCalligrapher() {
|
||||||
calligrapher.removeAll()
|
calligrapher.removeAll()
|
||||||
composer.clear()
|
composer.clear()
|
||||||
|
strCodePointBuffer.removeAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
func letComposerAndCalligrapherDoBackSpace() {
|
func letComposerAndCalligrapherDoBackSpace() {
|
||||||
|
|
|
@ -17,7 +17,9 @@ extension InputHandler {
|
||||||
/// - input: 輸入訊號。
|
/// - input: 輸入訊號。
|
||||||
/// - Returns: 告知 IMK「該按鍵是否已經被輸入法攔截處理」。
|
/// - Returns: 告知 IMK「該按鍵是否已經被輸入法攔截處理」。
|
||||||
func handleComposition(input: InputSignalProtocol) -> Bool? {
|
func handleComposition(input: InputSignalProtocol) -> Bool? {
|
||||||
prefs.cassetteEnabled ? handleCassetteComposition(input: input) : handlePhonabetComposition(input: input)
|
if isCodePointInputMode { return handleCodePointComposition(input: input) }
|
||||||
|
if prefs.cassetteEnabled { return handleCassetteComposition(input: input) }
|
||||||
|
return handlePhonabetComposition(input: input)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: 注音按鍵輸入處理 (Handle BPMF Keys)
|
// MARK: 注音按鍵輸入處理 (Handle BPMF Keys)
|
||||||
|
@ -305,4 +307,61 @@ extension InputHandler {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: 區位輸入處理 (Handle Code Point Input)
|
||||||
|
|
||||||
|
/// 用來處理 InputHandler.HandleInput() 當中的與區位輸入有關的組字行為。
|
||||||
|
/// - Parameters:
|
||||||
|
/// - input: 輸入訊號。
|
||||||
|
/// - Returns: 告知 IMK「該按鍵是否已經被輸入法攔截處理」。
|
||||||
|
private func handleCodePointComposition(input: InputSignalProtocol) -> Bool? {
|
||||||
|
guard !input.isReservedKey else { return nil }
|
||||||
|
guard let delegate = delegate, input.text.count == 1 else { return nil }
|
||||||
|
guard !input.text.compactMap(\.hexDigitValue).isEmpty else {
|
||||||
|
delegate.callError("05DD692C:輸入的字元並非 ASCII 字元。。")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
switch strCodePointBuffer.count {
|
||||||
|
case 0 ..< 4:
|
||||||
|
if strCodePointBuffer.count < 3 {
|
||||||
|
strCodePointBuffer.append(input.text)
|
||||||
|
var updatedState = generateStateOfInputting()
|
||||||
|
updatedState.tooltipDuration = 0
|
||||||
|
updatedState.tooltip = tooltipCodePointInputMode
|
||||||
|
delegate.switchState(updatedState)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
let encoding: CFStringEncodings? = {
|
||||||
|
switch IMEApp.currentInputMode {
|
||||||
|
case .imeModeCHS: return .GB_18030_2000
|
||||||
|
case .imeModeCHT: return .big5_HKSCS_1999
|
||||||
|
default: return nil
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
guard
|
||||||
|
var char = "\(strCodePointBuffer)\(input.text)"
|
||||||
|
.parsedAsHexLiteral(encoding: encoding)?.first?.description
|
||||||
|
else {
|
||||||
|
delegate.callError("D220B880:輸入的字碼沒有對應的字元。")
|
||||||
|
var updatedState = IMEState.ofAbortion()
|
||||||
|
updatedState.tooltipDuration = 0
|
||||||
|
updatedState.tooltip = "Invalid Code Point.".localized + " "
|
||||||
|
delegate.switchState(updatedState)
|
||||||
|
isCodePointInputMode = true
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// 某些舊版 macOS 會在這裡生成的字元後面插入垃圾字元。這裡只保留起始字元。
|
||||||
|
if char.count > 1 { char = char.map(\.description)[0] }
|
||||||
|
var updatedState = IMEState.ofCommitting(textToCommit: char)
|
||||||
|
updatedState.tooltipDuration = 0
|
||||||
|
updatedState.tooltip = tooltipCodePointInputMode
|
||||||
|
delegate.switchState(updatedState)
|
||||||
|
isCodePointInputMode = true
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
delegate.switchState(generateStateOfInputting())
|
||||||
|
isCodePointInputMode = true
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -162,8 +162,10 @@ extension InputHandler {
|
||||||
switch input.modifierFlags {
|
switch input.modifierFlags {
|
||||||
case []:
|
case []:
|
||||||
return handlePunctuationList(alternative: false, isJIS: isJIS)
|
return handlePunctuationList(alternative: false, isJIS: isJIS)
|
||||||
case [.option]:
|
case [.option, .shift]:
|
||||||
return handlePunctuationList(alternative: true, isJIS: isJIS)
|
return handlePunctuationList(alternative: true, isJIS: isJIS)
|
||||||
|
case .option:
|
||||||
|
return handleCodePointInputToggle()
|
||||||
default: break
|
default: break
|
||||||
}
|
}
|
||||||
case .kSpace: // 倘若沒有在偏好設定內將 Space 空格鍵設為選字窗呼叫用鍵的話………
|
case .kSpace: // 倘若沒有在偏好設定內將 Space 空格鍵設為選字窗呼叫用鍵的話………
|
||||||
|
|
|
@ -19,11 +19,16 @@ extension InputHandler {
|
||||||
|
|
||||||
/// 生成「正在輸入」狀態。相關的內容會被拿給狀態機械用來處理在電腦螢幕上顯示的內容。
|
/// 生成「正在輸入」狀態。相關的內容會被拿給狀態機械用來處理在電腦螢幕上顯示的內容。
|
||||||
public func generateStateOfInputting(sansReading: Bool = false) -> IMEStateProtocol {
|
public func generateStateOfInputting(sansReading: Bool = false) -> IMEStateProtocol {
|
||||||
|
let cpInput = isCodePointInputMode && !sansReading
|
||||||
/// 「更新內文組字區 (Update the composing buffer)」是指要求客體軟體將組字緩衝區的內容
|
/// 「更新內文組字區 (Update the composing buffer)」是指要求客體軟體將組字緩衝區的內容
|
||||||
/// 換成由此處重新生成的原始資料在 IMEStateData 當中生成的 NSAttributeString。
|
/// 換成由此處重新生成的原始資料在 IMEStateData 當中生成的 NSAttributeString。
|
||||||
var displayTextSegments: [String] = compositor.walkedNodes.values
|
var displayTextSegments: [String] = cpInput
|
||||||
var cursor = convertCursorForDisplay(compositor.cursor)
|
? [strCodePointBuffer]
|
||||||
let reading: String = sansReading ? "" : readingForDisplay // 先提出來,減輕運算負擔。
|
: compositor.walkedNodes.values
|
||||||
|
var cursor = cpInput
|
||||||
|
? displayTextSegments.joined().count
|
||||||
|
: convertCursorForDisplay(compositor.cursor)
|
||||||
|
let reading: String = (sansReading || isCodePointInputMode) ? "" : readingForDisplay // 先提出來,減輕運算負擔。
|
||||||
if !reading.isEmpty {
|
if !reading.isEmpty {
|
||||||
var newDisplayTextSegments = [String]()
|
var newDisplayTextSegments = [String]()
|
||||||
var temporaryNode = ""
|
var temporaryNode = ""
|
||||||
|
@ -287,6 +292,9 @@ extension InputHandler {
|
||||||
@discardableResult func handleEnter(input: InputSignalProtocol, readingOnly: Bool = false) -> Bool {
|
@discardableResult func handleEnter(input: InputSignalProtocol, readingOnly: Bool = false) -> Bool {
|
||||||
guard let delegate = delegate else { return false }
|
guard let delegate = delegate else { return false }
|
||||||
let state = delegate.state
|
let state = delegate.state
|
||||||
|
|
||||||
|
if isCodePointInputMode { return handleCodePointInputToggle() }
|
||||||
|
|
||||||
guard state.type == .ofInputting else { return false }
|
guard state.type == .ofInputting else { return false }
|
||||||
|
|
||||||
var displayedText = state.displayedText
|
var displayedText = state.displayedText
|
||||||
|
@ -384,7 +392,24 @@ extension InputHandler {
|
||||||
func handleBackSpace(input: InputSignalProtocol) -> Bool {
|
func handleBackSpace(input: InputSignalProtocol) -> Bool {
|
||||||
guard let delegate = delegate else { return false }
|
guard let delegate = delegate else { return false }
|
||||||
let state = delegate.state
|
let state = delegate.state
|
||||||
guard state.type == .ofInputting else { return false }
|
guard state.type == .ofInputting else {
|
||||||
|
isCodePointInputMode = false
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if isCodePointInputMode {
|
||||||
|
if !strCodePointBuffer.isEmpty {
|
||||||
|
strCodePointBuffer = strCodePointBuffer.dropLast(1).description
|
||||||
|
if !strCodePointBuffer.isEmpty {
|
||||||
|
var updatedState = generateStateOfInputting()
|
||||||
|
updatedState.tooltipDuration = 0
|
||||||
|
updatedState.tooltip = tooltipCodePointInputMode
|
||||||
|
delegate.switchState(updatedState)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return handleCodePointInputToggle()
|
||||||
|
}
|
||||||
|
|
||||||
// 引入 macOS 內建注音輸入法的行為,允許用 Shift+BackSpace 解構前一個漢字的讀音。
|
// 引入 macOS 內建注音輸入法的行為,允許用 Shift+BackSpace 解構前一個漢字的讀音。
|
||||||
shiftBksp: switch prefs.specifyShiftBackSpaceKeyBehavior {
|
shiftBksp: switch prefs.specifyShiftBackSpaceKeyBehavior {
|
||||||
|
@ -448,6 +473,9 @@ extension InputHandler {
|
||||||
func handleDelete(input: InputSignalProtocol) -> Bool {
|
func handleDelete(input: InputSignalProtocol) -> Bool {
|
||||||
guard let delegate = delegate else { return false }
|
guard let delegate = delegate else { return false }
|
||||||
let state = delegate.state
|
let state = delegate.state
|
||||||
|
|
||||||
|
if isCodePointInputMode { return handleCodePointInputToggle() }
|
||||||
|
|
||||||
guard state.type == .ofInputting else { return false }
|
guard state.type == .ofInputting else { return false }
|
||||||
|
|
||||||
if input.isShiftHold {
|
if input.isShiftHold {
|
||||||
|
@ -543,6 +571,9 @@ extension InputHandler {
|
||||||
func handleEsc() -> Bool {
|
func handleEsc() -> Bool {
|
||||||
guard let delegate = delegate else { return false }
|
guard let delegate = delegate else { return false }
|
||||||
let state = delegate.state
|
let state = delegate.state
|
||||||
|
|
||||||
|
if isCodePointInputMode { return handleCodePointInputToggle() }
|
||||||
|
|
||||||
guard state.type == .ofInputting else { return false }
|
guard state.type == .ofInputting else { return false }
|
||||||
|
|
||||||
if prefs.escToCleanInputBuffer {
|
if prefs.escToCleanInputBuffer {
|
||||||
|
@ -763,6 +794,25 @@ extension InputHandler {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - 處理區位輸入狀態的啟動過程
|
||||||
|
|
||||||
|
@discardableResult func handleCodePointInputToggle() -> Bool {
|
||||||
|
guard let delegate = delegate, delegate.state.type != .ofDeactivated else { return false }
|
||||||
|
if isCodePointInputMode {
|
||||||
|
isCodePointInputMode = false
|
||||||
|
delegate.switchState(IMEState.ofAbortion())
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
var updatedState = generateStateOfInputting(sansReading: true)
|
||||||
|
delegate.switchState(IMEState.ofCommitting(textToCommit: updatedState.displayedText))
|
||||||
|
updatedState = IMEState.ofEmpty()
|
||||||
|
updatedState.tooltipDuration = 0
|
||||||
|
updatedState.tooltip = tooltipCodePointInputMode
|
||||||
|
delegate.switchState(updatedState)
|
||||||
|
isCodePointInputMode = true
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - 處理符號選單
|
// MARK: - 處理符號選單
|
||||||
|
|
||||||
/// 處理符號選單。
|
/// 處理符號選單。
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
"vChewing" = "vChewing";
|
"vChewing" = "vChewing";
|
||||||
|
"Invalid Code Point." = "Invalid Code Point.";
|
||||||
|
"Code Point Input Mode." = "Code Point Input Mode.";
|
||||||
"You are about to uncheck this fart suppressor. You are responsible for all consequences lead by letting people nearby hear the fart sound come from your computer. We strongly advise against unchecking this in any public circumstance that prohibits NSFW netas." = "You are about to uncheck this fart suppressor. You are responsible for all consequences lead by letting people nearby hear the fart sound come from your computer. We strongly advise against unchecking this in any public circumstance that prohibits NSFW netas.";
|
"You are about to uncheck this fart suppressor. You are responsible for all consequences lead by letting people nearby hear the fart sound come from your computer. We strongly advise against unchecking this in any public circumstance that prohibits NSFW netas." = "You are about to uncheck this fart suppressor. You are responsible for all consequences lead by letting people nearby hear the fart sound come from your computer. We strongly advise against unchecking this in any public circumstance that prohibits NSFW netas.";
|
||||||
"Uncheck" = "Uncheck";
|
"Uncheck" = "Uncheck";
|
||||||
"Leave it checked" = "Leave it checked";
|
"Leave it checked" = "Leave it checked";
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
"vChewing" = "vChewing";
|
"vChewing" = "vChewing";
|
||||||
|
"Invalid Code Point." = "Invalid Code Point.";
|
||||||
|
"Code Point Input Mode." = "Code Point Input Mode.";
|
||||||
"You are about to uncheck this fart suppressor. You are responsible for all consequences lead by letting people nearby hear the fart sound come from your computer. We strongly advise against unchecking this in any public circumstance that prohibits NSFW netas." = "You are about to uncheck this fart suppressor. You are responsible for all consequences lead by letting people nearby hear the fart sound come from your computer. We strongly advise against unchecking this in any public circumstance that prohibits NSFW netas.";
|
"You are about to uncheck this fart suppressor. You are responsible for all consequences lead by letting people nearby hear the fart sound come from your computer. We strongly advise against unchecking this in any public circumstance that prohibits NSFW netas." = "You are about to uncheck this fart suppressor. You are responsible for all consequences lead by letting people nearby hear the fart sound come from your computer. We strongly advise against unchecking this in any public circumstance that prohibits NSFW netas.";
|
||||||
"Uncheck" = "Uncheck";
|
"Uncheck" = "Uncheck";
|
||||||
"Leave it checked" = "Leave it checked";
|
"Leave it checked" = "Leave it checked";
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
"vChewing" = "威注音入力アプリ";
|
"vChewing" = "威注音入力アプリ";
|
||||||
|
"Invalid Code Point." = "コードポイントは正しくない。";
|
||||||
|
"Code Point Input Mode." = "コードポイント入力。";
|
||||||
"You are about to uncheck this fart suppressor. You are responsible for all consequences lead by letting people nearby hear the fart sound come from your computer. We strongly advise against unchecking this in any public circumstance that prohibits NSFW netas." = "マナーモードのチェックの外すことになります。周りの人は貴殿のパソコンから屁音を聞いてしまい、それについての結果は全部貴殿の自己責任でございます。公的なる場所でこのチェックを外すのはお勧めできません。";
|
"You are about to uncheck this fart suppressor. You are responsible for all consequences lead by letting people nearby hear the fart sound come from your computer. We strongly advise against unchecking this in any public circumstance that prohibits NSFW netas." = "マナーモードのチェックの外すことになります。周りの人は貴殿のパソコンから屁音を聞いてしまい、それについての結果は全部貴殿の自己責任でございます。公的なる場所でこのチェックを外すのはお勧めできません。";
|
||||||
"Uncheck" = "チェックの外す";
|
"Uncheck" = "チェックの外す";
|
||||||
"Leave it checked" = "チェックをそのままに";
|
"Leave it checked" = "チェックをそのままに";
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
"vChewing" = "威注音输入法";
|
"vChewing" = "威注音输入法";
|
||||||
|
"Invalid Code Point." = "内码不正确。";
|
||||||
|
"Code Point Input Mode." = "内码输入模式。";
|
||||||
"You are about to uncheck this fart suppressor. You are responsible for all consequences lead by letting people nearby hear the fart sound come from your computer. We strongly advise against unchecking this in any public circumstance that prohibits NSFW netas." = "您即将取消对廉耻模式的勾选。周围的人会听到您的电脑传出放屁的声音,且您对此负全部责任。我们强烈建议您不要在任何公共场合取消对该模式的勾选。";
|
"You are about to uncheck this fart suppressor. You are responsible for all consequences lead by letting people nearby hear the fart sound come from your computer. We strongly advise against unchecking this in any public circumstance that prohibits NSFW netas." = "您即将取消对廉耻模式的勾选。周围的人会听到您的电脑传出放屁的声音,且您对此负全部责任。我们强烈建议您不要在任何公共场合取消对该模式的勾选。";
|
||||||
"Uncheck" = "取消勾选";
|
"Uncheck" = "取消勾选";
|
||||||
"Leave it checked" = "保持勾选";
|
"Leave it checked" = "保持勾选";
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
"vChewing" = "威注音輸入法";
|
"vChewing" = "威注音輸入法";
|
||||||
|
"Invalid Code Point." = "內碼不正確。";
|
||||||
|
"Code Point Input Mode." = "內碼輸入模式。";
|
||||||
"You are about to uncheck this fart suppressor. You are responsible for all consequences lead by letting people nearby hear the fart sound come from your computer. We strongly advise against unchecking this in any public circumstance that prohibits NSFW netas." = "您即將取消對廉恥模式的勾選。周圍的人會聽到您的電腦傳出放屁的聲音,且您對此負全部責任。我們強烈建議您不要在任何公共場合取消對該模式的勾選。";
|
"You are about to uncheck this fart suppressor. You are responsible for all consequences lead by letting people nearby hear the fart sound come from your computer. We strongly advise against unchecking this in any public circumstance that prohibits NSFW netas." = "您即將取消對廉恥模式的勾選。周圍的人會聽到您的電腦傳出放屁的聲音,且您對此負全部責任。我們強烈建議您不要在任何公共場合取消對該模式的勾選。";
|
||||||
"Uncheck" = "取消勾選";
|
"Uncheck" = "取消勾選";
|
||||||
"Leave it checked" = "保持勾選";
|
"Leave it checked" = "保持勾選";
|
||||||
|
|
Loading…
Reference in New Issue