InputHandler // +Enum: TypingMethod.
This commit is contained in:
parent
28e53c27ad
commit
e1b7a4df9f
|
@ -22,7 +22,6 @@ public protocol InputHandlerProtocol {
|
||||||
var currentLM: vChewingLM.LMInstantiator { get set }
|
var currentLM: vChewingLM.LMInstantiator { get set }
|
||||||
var currentUOM: vChewingLM.LMUserOverride { get set }
|
var currentUOM: vChewingLM.LMUserOverride { get set }
|
||||||
var delegate: InputHandlerDelegate? { get set }
|
var delegate: InputHandlerDelegate? { get set }
|
||||||
var composer: Tekkon.Composer { get set }
|
|
||||||
var keySeparator: String { get }
|
var keySeparator: String { get }
|
||||||
static var keySeparator: String { get }
|
static var keySeparator: String { get }
|
||||||
var isCompositorEmpty: Bool { get }
|
var isCompositorEmpty: Bool { get }
|
||||||
|
@ -89,13 +88,17 @@ public class InputHandler: InputHandlerProtocol {
|
||||||
|
|
||||||
/// 用來記錄「叫出選字窗前」的游標位置的變數。
|
/// 用來記錄「叫出選字窗前」的游標位置的變數。
|
||||||
var backupCursor: Int?
|
var backupCursor: Int?
|
||||||
|
/// 當前的打字模式。
|
||||||
|
var currentTypingMethod: TypingMethod = .vChewingFactory
|
||||||
|
|
||||||
/// 半衰模組的衰減指數
|
/// 半衰模組的衰減指數
|
||||||
let kEpsilon: Double = 0.000_001
|
let kEpsilon: Double = 0.000_001
|
||||||
|
|
||||||
public var calligrapher = "" // 磁帶專用組筆區
|
var strCodePointBuffer = "" // 內碼輸入專用組碼區
|
||||||
public var composer: Tekkon.Composer = .init() // 注拼槽
|
var calligrapher = "" // 磁帶專用組筆區
|
||||||
public var compositor: Megrez.Compositor // 組字器
|
var composer: Tekkon.Composer = .init() // 注拼槽
|
||||||
|
var compositor: Megrez.Compositor // 組字器
|
||||||
|
|
||||||
public var currentUOM: vChewingLM.LMUserOverride
|
public var currentUOM: vChewingLM.LMUserOverride
|
||||||
public var currentLM: vChewingLM.LMInstantiator {
|
public var currentLM: vChewingLM.LMInstantiator {
|
||||||
didSet {
|
didSet {
|
||||||
|
@ -120,43 +123,13 @@ public class InputHandler: InputHandlerProtocol {
|
||||||
public func clear() {
|
public func clear() {
|
||||||
clearComposerAndCalligrapher()
|
clearComposerAndCalligrapher()
|
||||||
compositor.clear()
|
compositor.clear()
|
||||||
isCodePointInputMode = false
|
currentTypingMethod = .vChewingFactory
|
||||||
isHaninKeyboardSymbolMode = false
|
|
||||||
backupCursor = nil
|
backupCursor = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 警告:該參數僅代指組音區/組筆區域與組字區在目前狀態下被視為「空」。
|
/// 警告:該參數僅代指組音區/組筆區域與組字區在目前狀態下被視為「空」。
|
||||||
var isConsideredEmptyForNow: Bool {
|
var isConsideredEmptyForNow: Bool {
|
||||||
compositor.isEmpty && isComposerOrCalligrapherEmpty && !isCodePointInputMode && !isHaninKeyboardSymbolMode
|
compositor.isEmpty && isComposerOrCalligrapherEmpty && currentTypingMethod == .vChewingFactory
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Hanin Keyboard Symbol Mode.
|
|
||||||
|
|
||||||
var isHaninKeyboardSymbolMode = false
|
|
||||||
|
|
||||||
static let tooltipHaninKeyboardSymbolMode: String = "\("Hanin Keyboard Symbol Input.".localized)"
|
|
||||||
|
|
||||||
// MARK: - Codepoint Input Buffer.
|
|
||||||
|
|
||||||
var isCodePointInputMode = false {
|
|
||||||
willSet {
|
|
||||||
strCodePointBuffer.removeAll()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var strCodePointBuffer = ""
|
|
||||||
|
|
||||||
var tooltipCodePointInputMode: String {
|
|
||||||
let commonTerm = NSMutableString()
|
|
||||||
commonTerm.insert("Code Point Input.".localized, at: 0)
|
|
||||||
if !(delegate?.isVerticalTyping ?? false) {
|
|
||||||
switch IMEApp.currentInputMode {
|
|
||||||
case .imeModeCHS: commonTerm.insert("[GB] ", at: 0)
|
|
||||||
case .imeModeCHT: commonTerm.insert("[Big5] ", at: 0)
|
|
||||||
default: break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return commonTerm.description
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Functions dealing with Megrez.
|
// MARK: - Functions dealing with Megrez.
|
||||||
|
|
|
@ -311,7 +311,8 @@ extension InputHandler {
|
||||||
if !updated { delegate.callError("66F3477B") }
|
if !updated { delegate.callError("66F3477B") }
|
||||||
return true
|
return true
|
||||||
case .option where state.type == .ofSymbolTable:
|
case .option where state.type == .ofSymbolTable:
|
||||||
return handleHaninKeyboardSymbolModeToggle()
|
// 繞過內碼輸入模式,直接進入漢音鍵盤符號模式。
|
||||||
|
return revolveTypingMethod(to: .haninKeyboardSymbol)
|
||||||
default: break
|
default: break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,37 +17,29 @@ extension InputHandler {
|
||||||
/// - Parameter input: 輸入訊號。
|
/// - Parameter input: 輸入訊號。
|
||||||
/// - Returns: 告知 IMK「該按鍵是否已經被輸入法攔截處理」。
|
/// - Returns: 告知 IMK「該按鍵是否已經被輸入法攔截處理」。
|
||||||
func handleComposition(input: InputSignalProtocol) -> Bool? {
|
func handleComposition(input: InputSignalProtocol) -> Bool? {
|
||||||
guard let delegate = delegate else { return nil }
|
|
||||||
// 不處理任何包含不可列印字元的訊號。
|
// 不處理任何包含不可列印字元的訊號。
|
||||||
guard !input.text.isEmpty, input.charCode.isPrintable else { return nil }
|
let hardRequirementMet = !input.text.isEmpty && input.charCode.isPrintable
|
||||||
if isCodePointInputMode { return handleCodePointComposition(input: input) }
|
switch currentTypingMethod {
|
||||||
if prefs.cassetteEnabled {
|
case .codePoint where hardRequirementMet:
|
||||||
// 準備處理 `%quick` 選字行為。
|
return handleCodePointComposition(input: input)
|
||||||
var handleQuickCandidate = true
|
case .haninKeyboardSymbol where [[], .shift].contains(input.keyModifierFlags):
|
||||||
if currentLM.areCassetteCandidateKeysShiftHeld { handleQuickCandidate = input.isShiftHold }
|
return handleHaninKeyboardSymbolModeInput(input: input)
|
||||||
let hasQuickCandidates: Bool = delegate.state.type == .ofInputting && delegate.state.isCandidateContainer
|
case .vChewingFactory where hardRequirementMet && prefs.cassetteEnabled:
|
||||||
|
|
||||||
// 處理 `%symboldef` 選字行為。
|
|
||||||
if handleCassetteSymbolTable(input: input) {
|
|
||||||
return true
|
|
||||||
} else if hasQuickCandidates, input.text != currentLM.cassetteWildcardKey {
|
|
||||||
// 處理 `%quick` 選字行為(當且僅當與 `%symboldef` 衝突的情況下)。
|
|
||||||
guard !(handleQuickCandidate && handleCandidate(input: input, ignoringModifiers: true)) else { return true }
|
|
||||||
} else {
|
|
||||||
// 處理 `%quick` 選字行為。
|
|
||||||
guard !(hasQuickCandidates && handleQuickCandidate && handleCandidate(input: input)) else { return true }
|
|
||||||
}
|
|
||||||
return handleCassetteComposition(input: input)
|
return handleCassetteComposition(input: input)
|
||||||
|
case .vChewingFactory where hardRequirementMet && !prefs.cassetteEnabled:
|
||||||
|
return handlePhonabetComposition(input: input)
|
||||||
|
default: return nil
|
||||||
}
|
}
|
||||||
return handlePhonabetComposition(input: input)
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: 注音按鍵輸入處理 (Handle BPMF Keys)
|
// MARK: - 注音按鍵輸入處理 (Handle BPMF Keys)
|
||||||
|
|
||||||
|
private extension InputHandler {
|
||||||
/// 用來處理 InputHandler.HandleInput() 當中的與注音输入有關的組字行為。
|
/// 用來處理 InputHandler.HandleInput() 當中的與注音输入有關的組字行為。
|
||||||
/// - Parameter input: 輸入訊號。
|
/// - Parameter input: 輸入訊號。
|
||||||
/// - Returns: 告知 IMK「該按鍵是否已經被輸入法攔截處理」。
|
/// - Returns: 告知 IMK「該按鍵是否已經被輸入法攔截處理」。
|
||||||
private func handlePhonabetComposition(input: InputSignalProtocol) -> Bool? {
|
func handlePhonabetComposition(input: InputSignalProtocol) -> Bool? {
|
||||||
guard let delegate = delegate else { return nil }
|
guard let delegate = delegate else { return nil }
|
||||||
var inputText = (input.inputTextIgnoringModifiers ?? input.text)
|
var inputText = (input.inputTextIgnoringModifiers ?? input.text)
|
||||||
inputText = inputText.lowercased().applyingTransformFW2HW(reverse: false)
|
inputText = inputText.lowercased().applyingTransformFW2HW(reverse: false)
|
||||||
|
@ -241,13 +233,31 @@ extension InputHandler {
|
||||||
|
|
||||||
// MARK: - 磁帶模式的組字支援。
|
// MARK: - 磁帶模式的組字支援。
|
||||||
|
|
||||||
extension InputHandler {
|
private extension InputHandler {
|
||||||
/// 用來處理 InputHandler.HandleInput() 當中的與磁帶模組有關的組字行為。
|
/// 用來處理 InputHandler.HandleInput() 當中的與磁帶模組有關的組字行為。(前置處理)
|
||||||
/// - Parameter input: 輸入訊號。
|
/// - Parameter input: 輸入訊號。
|
||||||
/// - Returns: 告知 IMK「該按鍵是否已經被輸入法攔截處理」。
|
/// - Returns: 告知 IMK「該按鍵是否已經被輸入法攔截處理」。
|
||||||
private func handleCassetteComposition(input: InputSignalProtocol) -> Bool? {
|
func handleCassetteComposition(input: InputSignalProtocol) -> Bool? {
|
||||||
guard let delegate = delegate else { return nil }
|
guard let delegate = delegate else { return nil }
|
||||||
let state = delegate.state
|
let state = delegate.state
|
||||||
|
|
||||||
|
// 準備處理 `%quick` 選字行為。
|
||||||
|
var handleQuickCandidate = true
|
||||||
|
if currentLM.areCassetteCandidateKeysShiftHeld { handleQuickCandidate = input.isShiftHold }
|
||||||
|
let hasQuickCandidates: Bool = state.type == .ofInputting && state.isCandidateContainer
|
||||||
|
|
||||||
|
// 處理 `%symboldef` 選字行為。
|
||||||
|
if handleCassetteSymbolTable(input: input) {
|
||||||
|
return true
|
||||||
|
} else if hasQuickCandidates, input.text != currentLM.cassetteWildcardKey {
|
||||||
|
// 處理 `%quick` 選字行為(當且僅當與 `%symboldef` 衝突的情況下)。
|
||||||
|
guard !(handleQuickCandidate && handleCandidate(input: input, ignoringModifiers: true)) else { return true }
|
||||||
|
} else {
|
||||||
|
// 處理 `%quick` 選字行為。
|
||||||
|
guard !(hasQuickCandidates && handleQuickCandidate && handleCandidate(input: input)) else { return true }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 正式處理。
|
||||||
var wildcardKey: String { currentLM.cassetteWildcardKey } // 花牌鍵。
|
var wildcardKey: String { currentLM.cassetteWildcardKey } // 花牌鍵。
|
||||||
let inputText = input.text
|
let inputText = input.text
|
||||||
let isWildcardKeyInput: Bool = (inputText == wildcardKey && !wildcardKey.isEmpty)
|
let isWildcardKeyInput: Bool = (inputText == wildcardKey && !wildcardKey.isEmpty)
|
||||||
|
@ -266,7 +276,7 @@ extension InputHandler {
|
||||||
calligrapher.count >= currentLM.maxCassetteKeyLength || isLongestPossibleKeyFormed
|
calligrapher.count >= currentLM.maxCassetteKeyLength || isLongestPossibleKeyFormed
|
||||||
}
|
}
|
||||||
|
|
||||||
prehandling: if !skipStrokeHandling && currentLM.isThisCassetteKeyAllowed(key: inputText) {
|
prehandling: if !skipStrokeHandling && currentLM.isThisCassetteKeyAllowed(key: inputText) {
|
||||||
if calligrapher.isEmpty, isWildcardKeyInput {
|
if calligrapher.isEmpty, isWildcardKeyInput {
|
||||||
delegate.callError("3606B9C0")
|
delegate.callError("3606B9C0")
|
||||||
if input.beganWithLetter {
|
if input.beganWithLetter {
|
||||||
|
@ -385,16 +395,17 @@ extension InputHandler {
|
||||||
// 將「這個按鍵訊號已經被輸入法攔截處理了」的結果藉由 SessionCtl 回報給 IMK。
|
// 將「這個按鍵訊號已經被輸入法攔截處理了」的結果藉由 SessionCtl 回報給 IMK。
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: 內碼區位輸入處理 (Handle Code Point Input)
|
// MARK: - 內碼區位輸入處理 (Handle Code Point Input)
|
||||||
|
|
||||||
|
private extension InputHandler {
|
||||||
/// 用來處理 InputHandler.HandleInput() 當中的與內碼區位輸入有關的組字行為。
|
/// 用來處理 InputHandler.HandleInput() 當中的與內碼區位輸入有關的組字行為。
|
||||||
/// - Parameter input: 輸入訊號。
|
/// - Parameter input: 輸入訊號。
|
||||||
/// - Returns: 告知 IMK「該按鍵是否已經被輸入法攔截處理」。
|
/// - Returns: 告知 IMK「該按鍵是否已經被輸入法攔截處理」。
|
||||||
private func handleCodePointComposition(input: InputSignalProtocol) -> Bool? {
|
func handleCodePointComposition(input: InputSignalProtocol) -> Bool? {
|
||||||
guard !input.isReservedKey else { return nil }
|
guard !input.isReservedKey else { return nil }
|
||||||
guard let delegate = delegate, input.text.count == 1 else { return nil }
|
guard let delegate = delegate, input.text.count == 1 else { return nil }
|
||||||
guard !input.text.compactMap(\.hexDigitValue).isEmpty else {
|
guard !input.text.compactMap(\.hexDigitValue).isEmpty else {
|
||||||
|
@ -407,7 +418,7 @@ extension InputHandler {
|
||||||
strCodePointBuffer.append(input.text)
|
strCodePointBuffer.append(input.text)
|
||||||
var updatedState = generateStateOfInputting(guarded: true)
|
var updatedState = generateStateOfInputting(guarded: true)
|
||||||
updatedState.tooltipDuration = 0
|
updatedState.tooltipDuration = 0
|
||||||
updatedState.tooltip = tooltipCodePointInputMode
|
updatedState.tooltip = TypingMethod.codePoint.getTooltip(vertical: delegate.isVerticalTyping)
|
||||||
delegate.switchState(updatedState)
|
delegate.switchState(updatedState)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -427,7 +438,7 @@ extension InputHandler {
|
||||||
updatedState.tooltipDuration = 0
|
updatedState.tooltipDuration = 0
|
||||||
updatedState.tooltip = "Invalid Code Point.".localized
|
updatedState.tooltip = "Invalid Code Point.".localized
|
||||||
delegate.switchState(updatedState)
|
delegate.switchState(updatedState)
|
||||||
isCodePointInputMode = true
|
currentTypingMethod = .codePoint
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
// 某些舊版 macOS 會在這裡生成的字元後面插入垃圾字元。這裡只保留起始字元。
|
// 某些舊版 macOS 會在這裡生成的字元後面插入垃圾字元。這裡只保留起始字元。
|
||||||
|
@ -435,14 +446,46 @@ extension InputHandler {
|
||||||
delegate.switchState(IMEState.ofCommitting(textToCommit: char))
|
delegate.switchState(IMEState.ofCommitting(textToCommit: char))
|
||||||
var updatedState = generateStateOfInputting(guarded: true)
|
var updatedState = generateStateOfInputting(guarded: true)
|
||||||
updatedState.tooltipDuration = 0
|
updatedState.tooltipDuration = 0
|
||||||
updatedState.tooltip = tooltipCodePointInputMode
|
updatedState.tooltip = TypingMethod.codePoint.getTooltip(vertical: delegate.isVerticalTyping)
|
||||||
delegate.switchState(updatedState)
|
delegate.switchState(updatedState)
|
||||||
isCodePointInputMode = true
|
currentTypingMethod = .codePoint
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
delegate.switchState(generateStateOfInputting())
|
delegate.switchState(generateStateOfInputting())
|
||||||
isCodePointInputMode = true
|
currentTypingMethod = .codePoint
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - 處理漢音鍵盤符號輸入狀態(Handle Hanin Keyboard Symbol Inputs)
|
||||||
|
|
||||||
|
private extension InputHandler {
|
||||||
|
/// 處理漢音鍵盤符號輸入。
|
||||||
|
/// - Parameters:
|
||||||
|
/// - input: 輸入按鍵訊號。
|
||||||
|
/// - Returns: 將按鍵行為「是否有處理掉」藉由 SessionCtl 回報給 IMK。
|
||||||
|
func handleHaninKeyboardSymbolModeInput(input: InputSignalProtocol) -> Bool {
|
||||||
|
guard let delegate = delegate, delegate.state.type != .ofDeactivated else { return false }
|
||||||
|
let charText = input.text.lowercased().applyingTransformFW2HW(reverse: false)
|
||||||
|
guard CandidateNode.mapHaninKeyboardSymbols.keys.contains(charText) else {
|
||||||
|
return revolveTypingMethod(to: .vChewingFactory)
|
||||||
|
}
|
||||||
|
guard
|
||||||
|
charText.count == 1, let symbols = CandidateNode.queryHaninKeyboardSymbols(char: charText)
|
||||||
|
else {
|
||||||
|
delegate.callError("C1A760C7")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// 得在這裡先 commit buffer,不然會導致「在摁 ESC 離開符號選單時會重複輸入上一次的組字區的內容」的不當行為。
|
||||||
|
let textToCommit = generateStateOfInputting(sansReading: true).displayedText
|
||||||
|
delegate.switchState(IMEState.ofCommitting(textToCommit: textToCommit))
|
||||||
|
if symbols.members.count == 1 {
|
||||||
|
delegate.switchState(IMEState.ofCommitting(textToCommit: symbols.members.map(\.name).joined()))
|
||||||
|
} else {
|
||||||
|
delegate.switchState(IMEState.ofSymbolTable(node: symbols))
|
||||||
|
}
|
||||||
|
currentTypingMethod = .vChewingFactory // 用完就關掉,但保持選字窗開啟,所以這裡不用呼叫 toggle 函式。
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -27,17 +27,18 @@ extension InputHandler {
|
||||||
if isConsideredEmptyForNow, !guarded { return IMEState.ofAbortion() }
|
if isConsideredEmptyForNow, !guarded { return IMEState.ofAbortion() }
|
||||||
restoreBackupCursor() // 只要叫了 Inputting 狀態,就盡可能還原游標備份。
|
restoreBackupCursor() // 只要叫了 Inputting 狀態,就盡可能還原游標備份。
|
||||||
var segHighlightedAt: Int?
|
var segHighlightedAt: Int?
|
||||||
let cpInput = isCodePointInputMode && !sansReading
|
let handleAsCodePointInput = currentTypingMethod == .codePoint && !sansReading
|
||||||
/// 「更新內文組字區 (Update the composing buffer)」是指要求客體軟體將組字緩衝區的內容
|
/// 「更新內文組字區 (Update the composing buffer)」是指要求客體軟體將組字緩衝區的內容
|
||||||
/// 換成由此處重新生成的原始資料在 IMEStateData 當中生成的 NSAttributeString。
|
/// 換成由此處重新生成的原始資料在 IMEStateData 當中生成的 NSAttributeString。
|
||||||
var displayTextSegments: [String] = cpInput
|
var displayTextSegments: [String] = handleAsCodePointInput
|
||||||
? [strCodePointBuffer]
|
? [strCodePointBuffer]
|
||||||
: compositor.walkedNodes.values
|
: compositor.walkedNodes.values
|
||||||
var cursor = cpInput
|
var cursor = handleAsCodePointInput
|
||||||
? displayTextSegments.joined().count
|
? displayTextSegments.joined().count
|
||||||
: convertCursorForDisplay(compositor.cursor)
|
: convertCursorForDisplay(compositor.cursor)
|
||||||
let cursorSansReading = cursor
|
let cursorSansReading = cursor
|
||||||
let reading: String = (sansReading || isCodePointInputMode) ? "" : readingForDisplay // 先提出來,減輕運算負擔。
|
// 先提出來讀音資料,減輕運算負擔。
|
||||||
|
let reading: String = (sansReading || currentTypingMethod == .codePoint) ? "" : readingForDisplay
|
||||||
if !reading.isEmpty {
|
if !reading.isEmpty {
|
||||||
var newDisplayTextSegments = [String]()
|
var newDisplayTextSegments = [String]()
|
||||||
var temporaryNode = ""
|
var temporaryNode = ""
|
||||||
|
@ -361,8 +362,9 @@ extension InputHandler {
|
||||||
guard let delegate = delegate else { return false }
|
guard let delegate = delegate else { return false }
|
||||||
let state = delegate.state
|
let state = delegate.state
|
||||||
|
|
||||||
if isHaninKeyboardSymbolMode { return handleHaninKeyboardSymbolModeToggle() }
|
guard currentTypingMethod == .vChewingFactory else {
|
||||||
if isCodePointInputMode { return handleCodePointInputToggle() }
|
return revolveTypingMethod(to: .vChewingFactory)
|
||||||
|
}
|
||||||
|
|
||||||
guard state.type == .ofInputting else { return false }
|
guard state.type == .ofInputting else { return false }
|
||||||
|
|
||||||
|
@ -470,31 +472,28 @@ extension InputHandler {
|
||||||
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 {
|
guard state.type == .ofInputting else {
|
||||||
isCodePointInputMode = false
|
currentTypingMethod = .vChewingFactory
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if isCodePointInputMode {
|
if currentTypingMethod == .codePoint {
|
||||||
if !strCodePointBuffer.isEmpty {
|
if !strCodePointBuffer.isEmpty {
|
||||||
func refreshState() {
|
func refreshState() {
|
||||||
var updatedState = generateStateOfInputting(guarded: true)
|
var updatedState = generateStateOfInputting(guarded: true)
|
||||||
updatedState.tooltipDuration = 0
|
updatedState.tooltipDuration = 0
|
||||||
updatedState.tooltip = tooltipCodePointInputMode
|
updatedState.tooltip = delegate.state.tooltip
|
||||||
delegate.switchState(updatedState)
|
delegate.switchState(updatedState)
|
||||||
}
|
}
|
||||||
strCodePointBuffer = strCodePointBuffer.dropLast(1).description
|
strCodePointBuffer = strCodePointBuffer.dropLast(1).description
|
||||||
if input.commonKeyModifierFlags == .option {
|
if input.commonKeyModifierFlags == .option {
|
||||||
strCodePointBuffer.removeAll()
|
return revolveTypingMethod(to: .codePoint)
|
||||||
refreshState()
|
|
||||||
isCodePointInputMode = true
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
if !strCodePointBuffer.isEmpty {
|
if !strCodePointBuffer.isEmpty {
|
||||||
refreshState()
|
refreshState()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return handleCodePointInputToggle()
|
return revolveTypingMethod(to: .vChewingFactory)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 引入 macOS 內建注音輸入法的行為,允許用 Shift+BackSpace 解構前一個漢字的讀音。
|
// 引入 macOS 內建注音輸入法的行為,允許用 Shift+BackSpace 解構前一個漢字的讀音。
|
||||||
|
@ -575,8 +574,9 @@ extension InputHandler {
|
||||||
guard let delegate = delegate else { return false }
|
guard let delegate = delegate else { return false }
|
||||||
let state = delegate.state
|
let state = delegate.state
|
||||||
|
|
||||||
if isHaninKeyboardSymbolMode { return handleHaninKeyboardSymbolModeToggle() }
|
guard currentTypingMethod == .vChewingFactory else {
|
||||||
if isCodePointInputMode { return handleCodePointInputToggle() }
|
return revolveTypingMethod(to: .vChewingFactory)
|
||||||
|
}
|
||||||
|
|
||||||
guard state.type == .ofInputting else { return false }
|
guard state.type == .ofInputting else { return false }
|
||||||
|
|
||||||
|
@ -684,8 +684,9 @@ extension InputHandler {
|
||||||
guard let delegate = delegate else { return false }
|
guard let delegate = delegate else { return false }
|
||||||
let state = delegate.state
|
let state = delegate.state
|
||||||
|
|
||||||
if isHaninKeyboardSymbolMode { return handleHaninKeyboardSymbolModeToggle() }
|
guard currentTypingMethod == .vChewingFactory else {
|
||||||
if isCodePointInputMode { return handleCodePointInputToggle() }
|
return revolveTypingMethod(to: .vChewingFactory)
|
||||||
|
}
|
||||||
|
|
||||||
guard state.type == .ofInputting else { return false }
|
guard state.type == .ofInputting else { return false }
|
||||||
|
|
||||||
|
@ -920,73 +921,6 @@ extension InputHandler {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 處理內碼區位輸入狀態的啟動過程(CodePoint Input Toggle)
|
|
||||||
|
|
||||||
@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 = generateStateOfInputting(guarded: true)
|
|
||||||
updatedState.tooltipDuration = 0
|
|
||||||
updatedState.tooltip = tooltipCodePointInputMode
|
|
||||||
delegate.switchState(updatedState)
|
|
||||||
isCodePointInputMode = true
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - 處理漢音鍵盤符號輸入狀態的啟動過程(Hanin Pallete)
|
|
||||||
|
|
||||||
@discardableResult func handleHaninKeyboardSymbolModeToggle() -> Bool {
|
|
||||||
guard let delegate = delegate, delegate.state.type != .ofDeactivated else { return false }
|
|
||||||
if isCodePointInputMode { isCodePointInputMode = false }
|
|
||||||
if isHaninKeyboardSymbolMode {
|
|
||||||
isHaninKeyboardSymbolMode = false
|
|
||||||
delegate.switchState(IMEState.ofAbortion())
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
var updatedState = generateStateOfInputting(sansReading: true)
|
|
||||||
delegate.switchState(IMEState.ofCommitting(textToCommit: updatedState.displayedText))
|
|
||||||
updatedState = generateStateOfInputting(guarded: true)
|
|
||||||
updatedState.tooltipDuration = 0
|
|
||||||
updatedState.tooltip = Self.tooltipHaninKeyboardSymbolMode
|
|
||||||
delegate.switchState(updatedState)
|
|
||||||
isHaninKeyboardSymbolMode = true
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 處理漢音鍵盤符號輸入。
|
|
||||||
/// - Parameters:
|
|
||||||
/// - input: 輸入按鍵訊號。
|
|
||||||
/// - Returns: 將按鍵行為「是否有處理掉」藉由 SessionCtl 回報給 IMK。
|
|
||||||
func handleHaninKeyboardSymbolModeInput(input: InputSignalProtocol) -> Bool {
|
|
||||||
guard let delegate = delegate, delegate.state.type != .ofDeactivated else { return false }
|
|
||||||
let charText = input.text.lowercased().applyingTransformFW2HW(reverse: false)
|
|
||||||
guard CandidateNode.mapHaninKeyboardSymbols.keys.contains(charText) else {
|
|
||||||
return handleHaninKeyboardSymbolModeToggle()
|
|
||||||
}
|
|
||||||
guard
|
|
||||||
charText.count == 1, let symbols = CandidateNode.queryHaninKeyboardSymbols(char: charText)
|
|
||||||
else {
|
|
||||||
delegate.callError("C1A760C7")
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
// 得在這裡先 commit buffer,不然會導致「在摁 ESC 離開符號選單時會重複輸入上一次的組字區的內容」的不當行為。
|
|
||||||
let textToCommit = generateStateOfInputting(sansReading: true).displayedText
|
|
||||||
delegate.switchState(IMEState.ofCommitting(textToCommit: textToCommit))
|
|
||||||
if symbols.members.count == 1 {
|
|
||||||
delegate.switchState(IMEState.ofCommitting(textToCommit: symbols.members.map(\.name).joined()))
|
|
||||||
} else {
|
|
||||||
delegate.switchState(IMEState.ofSymbolTable(node: symbols))
|
|
||||||
}
|
|
||||||
isHaninKeyboardSymbolMode = false // 用完就關掉,但保持選字窗開啟,所以這裡不用呼叫 toggle 函式。
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - 處理符號選單(Symbol Menu Input)
|
// MARK: - 處理符號選單(Symbol Menu Input)
|
||||||
|
|
||||||
/// 處理符號選單。
|
/// 處理符號選單。
|
||||||
|
|
|
@ -48,7 +48,7 @@ public extension InputHandler {
|
||||||
case .kCarriageReturn, .kLineFeed:
|
case .kCarriageReturn, .kLineFeed:
|
||||||
let frontNode = compositor.walkedNodes.last
|
let frontNode = compositor.walkedNodes.last
|
||||||
return handleEnter(input: input) {
|
return handleEnter(input: input) {
|
||||||
guard !self.isHaninKeyboardSymbolMode, !self.isCodePointInputMode else { return [] }
|
guard self.currentTypingMethod == .vChewingFactory else { return [] }
|
||||||
guard let frontNode = frontNode else { return [] }
|
guard let frontNode = frontNode else { return [] }
|
||||||
let pair = Megrez.KeyValuePaired(keyArray: frontNode.keyArray, value: frontNode.value)
|
let pair = Megrez.KeyValuePaired(keyArray: frontNode.keyArray, value: frontNode.value)
|
||||||
let associates = self.generateArrayOfAssociates(withPair: pair)
|
let associates = self.generateArrayOfAssociates(withPair: pair)
|
||||||
|
@ -62,13 +62,7 @@ public extension InputHandler {
|
||||||
case [.option, .shift]:
|
case [.option, .shift]:
|
||||||
return handlePunctuationList(alternative: true, isJIS: isJIS)
|
return handlePunctuationList(alternative: true, isJIS: isJIS)
|
||||||
case .option:
|
case .option:
|
||||||
switch (isCodePointInputMode, isHaninKeyboardSymbolMode) {
|
return revolveTypingMethod()
|
||||||
case (false, false): return handleCodePointInputToggle()
|
|
||||||
case (true, false), (false, true):
|
|
||||||
return handleHaninKeyboardSymbolModeToggle()
|
|
||||||
default: break
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
default: break
|
default: break
|
||||||
}
|
}
|
||||||
case .kSpace:
|
case .kSpace:
|
||||||
|
@ -85,7 +79,7 @@ public extension InputHandler {
|
||||||
if input.isShiftHold, !input.isControlHold, !input.isOptionHold {
|
if input.isShiftHold, !input.isControlHold, !input.isOptionHold {
|
||||||
return revolveCandidate(reverseOrder: input.isCommandHold)
|
return revolveCandidate(reverseOrder: input.isCommandHold)
|
||||||
}
|
}
|
||||||
if isCodePointInputMode {
|
if currentTypingMethod == .codePoint {
|
||||||
delegate.callError("FDD88EDB")
|
delegate.callError("FDD88EDB")
|
||||||
delegate.switchState(IMEState.ofAbortion())
|
delegate.switchState(IMEState.ofAbortion())
|
||||||
return true
|
return true
|
||||||
|
@ -148,13 +142,11 @@ public extension InputHandler {
|
||||||
guard let x = input.inputTextIgnoringModifiers,
|
guard let x = input.inputTextIgnoringModifiers,
|
||||||
"¥\\".contains(x), input.keyModifierFlags.isEmpty
|
"¥\\".contains(x), input.keyModifierFlags.isEmpty
|
||||||
else { break haninSymbolInput }
|
else { break haninSymbolInput }
|
||||||
return handleHaninKeyboardSymbolModeToggle()
|
return revolveTypingMethod(to: .haninKeyboardSymbol)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 注音按鍵輸入與漢音鍵盤符號輸入處理。
|
// 注音/磁帶按鍵輸入與漢音鍵盤符號輸入處理。
|
||||||
if isHaninKeyboardSymbolMode, [[], .shift].contains(input.keyModifierFlags) {
|
if let compositionHandled = handleComposition(input: input) {
|
||||||
return handleHaninKeyboardSymbolModeInput(input: input)
|
|
||||||
} else if let compositionHandled = handleComposition(input: input) {
|
|
||||||
return compositionHandled
|
return compositionHandled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||||
|
// ====================
|
||||||
|
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
|
||||||
|
// ... with NTL restriction stating that:
|
||||||
|
// No trademark license is granted to use the trade names, trademarks, service
|
||||||
|
// marks, or product names of Contributor, except as required to fulfill notice
|
||||||
|
// requirements defined in MIT License.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import SwiftExtension
|
||||||
|
|
||||||
|
// MARK: - Typing Method
|
||||||
|
|
||||||
|
public extension InputHandler {
|
||||||
|
enum TypingMethod: Int, CaseIterable {
|
||||||
|
case vChewingFactory // 自動指派: 0
|
||||||
|
case codePoint // 自動指派: 1
|
||||||
|
case haninKeyboardSymbol // 自動指派: 2
|
||||||
|
|
||||||
|
mutating func revolveNext() {
|
||||||
|
var theInt = rawValue
|
||||||
|
theInt.revolveAsIndex(with: Self.allCases)
|
||||||
|
guard let nextMethod = TypingMethod(rawValue: theInt) else { return }
|
||||||
|
self = nextMethod
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTooltip(vertical: Bool = false) -> String {
|
||||||
|
switch self {
|
||||||
|
case .vChewingFactory: return ""
|
||||||
|
case .codePoint:
|
||||||
|
let commonTerm = NSMutableString()
|
||||||
|
commonTerm.insert("Code Point Input.".localized, at: 0)
|
||||||
|
if !vertical {
|
||||||
|
switch IMEApp.currentInputMode {
|
||||||
|
case .imeModeCHS: commonTerm.insert("[GB] ", at: 0)
|
||||||
|
case .imeModeCHT: commonTerm.insert("[Big5] ", at: 0)
|
||||||
|
default: break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return commonTerm.description
|
||||||
|
case .haninKeyboardSymbol:
|
||||||
|
return "\("Hanin Keyboard Symbol Input.".localized)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Handle Rotation Toggles
|
||||||
|
|
||||||
|
public extension InputHandler {
|
||||||
|
@discardableResult func revolveTypingMethod(to specifiedMethod: TypingMethod? = nil) -> Bool {
|
||||||
|
guard let delegate = delegate else { return false }
|
||||||
|
var newMethod = currentTypingMethod
|
||||||
|
if let specified = specifiedMethod {
|
||||||
|
newMethod = specified
|
||||||
|
} else {
|
||||||
|
newMethod.revolveNext()
|
||||||
|
}
|
||||||
|
/// 接下來這行必須這樣 defer 處理,
|
||||||
|
/// 因為再接下來的 switch newMethod 的過程會影響到 currentTypingMethod 參數。
|
||||||
|
defer {
|
||||||
|
currentTypingMethod = newMethod
|
||||||
|
}
|
||||||
|
switch newMethod {
|
||||||
|
case .vChewingFactory:
|
||||||
|
delegate.switchState(IMEState.ofAbortion())
|
||||||
|
return true
|
||||||
|
case .codePoint:
|
||||||
|
strCodePointBuffer.removeAll()
|
||||||
|
case .haninKeyboardSymbol: break
|
||||||
|
}
|
||||||
|
var updatedState = generateStateOfInputting(sansReading: true)
|
||||||
|
delegate.switchState(IMEState.ofCommitting(textToCommit: updatedState.displayedText))
|
||||||
|
updatedState = generateStateOfInputting(guarded: true)
|
||||||
|
updatedState.tooltipDuration = 0
|
||||||
|
updatedState.tooltip = newMethod.getTooltip(vertical: delegate.isVerticalTyping)
|
||||||
|
delegate.switchState(updatedState)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue