Repo // Moving input handling instructions to InputHandler.
This commit is contained in:
parent
5832136e05
commit
6e94cbea55
|
@ -20,7 +20,9 @@ import Tekkon
|
|||
/// InputHandler 委任協定
|
||||
public protocol InputHandlerDelegate {
|
||||
var selectionKeys: String { get }
|
||||
var state: IMEStateProtocol { get set }
|
||||
var clientBundleIdentifier: String { get }
|
||||
func handle(state newState: IMEStateProtocol, replaceCurrent: Bool)
|
||||
func candidateController() -> CtlCandidateProtocol
|
||||
func candidateSelectionCalledByInputHandler(at index: Int)
|
||||
func performUserPhraseOperation(with state: IMEStateProtocol, addToFilter: Bool)
|
||||
|
|
|
@ -0,0 +1,143 @@
|
|||
// (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.
|
||||
|
||||
/// 該檔案乃按鍵調度模組當中用來預處理 NSEvent 的模組。
|
||||
|
||||
import InputMethodKit
|
||||
import Shared
|
||||
|
||||
// MARK: - § 根據狀態調度按鍵輸入 (Handle Input with States)
|
||||
|
||||
extension InputHandler {
|
||||
/// 分診函式,會先確認是否是 IMK 選字窗要處理的事件、然後再決定處理步驟。
|
||||
/// - Parameter event: 由 IMK 選字窗接收的裝置操作輸入事件。
|
||||
/// - Returns: 回「`true`」以將該案件已攔截處理的訊息傳遞給 IMK;回「`false`」則放行、不作處理。
|
||||
public func handleEvent(_ event: NSEvent) -> Bool {
|
||||
imkCandidatesEventPreHandler(event: event) ?? commonEventHandler(event)
|
||||
}
|
||||
|
||||
/// 將按鍵行為與當前輸入法狀態結合起來、交給按鍵調度模組來處理。
|
||||
/// 再根據返回的 result bool 數值來告知 IMK「這個按鍵事件是被處理了還是被放行了」。
|
||||
/// 這裡不用 handleCandidate() 是因為需要針對聯想詞輸入狀態做額外處理。
|
||||
private func commonEventHandler(_ event: NSEvent) -> Bool {
|
||||
guard let delegate = delegate else { return false }
|
||||
|
||||
let result = handleInput(event: event, state: delegate.state) { newState in
|
||||
delegate.handle(state: newState, replaceCurrent: true)
|
||||
} errorCallback: { errorString in
|
||||
vCLog(errorString)
|
||||
IMEApp.buzz()
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/// 專門處理與 IMK 選字窗有關的判斷語句。
|
||||
/// 這樣分開處理很有必要,不然 handle() 函式會陷入無限迴圈。
|
||||
/// - Parameter event: 由 IMK 選字窗接收的裝置操作輸入事件。
|
||||
/// - Returns: 回「`true`」以將該案件已攔截處理的訊息傳遞給 IMK;回「`false`」則放行、不作處理。
|
||||
private func imkCandidatesEventPreHandler(event eventToDeal: NSEvent) -> Bool? {
|
||||
guard let delegate = delegate else { return false }
|
||||
|
||||
// IMK 選字窗處理,當且僅當啟用了 IMK 選字窗的時候才會生效。
|
||||
// 這樣可以讓 interpretKeyEvents() 函式自行判斷:
|
||||
// - 是就地交給 imkCandidates.interpretKeyEvents() 處理?
|
||||
// - 還是藉由 delegate 扔回 SessionCtl 給 InputHandler 處理?
|
||||
if let imkCandidates = delegate.candidateController() as? CtlCandidateIMK, imkCandidates.visible {
|
||||
let event: NSEvent = CtlCandidateIMK.replaceNumPadKeyCodes(target: eventToDeal) ?? eventToDeal
|
||||
|
||||
// Shift+Enter 是個特殊情形,不提前攔截處理的話、會有垃圾參數傳給 delegate 的 inputHandler 從而崩潰。
|
||||
// 所以這裡直接將 Shift Flags 清空。
|
||||
if event.isShiftHold, event.isEnter {
|
||||
guard let newEvent = event.reinitiate(modifierFlags: []) else {
|
||||
IMEApp.buzz()
|
||||
return true
|
||||
}
|
||||
|
||||
return imkCandidatesEventSubHandler(event: newEvent)
|
||||
}
|
||||
|
||||
// 聯想詞選字。
|
||||
if let newChar = CtlCandidateIMK.defaultIMKSelectionKey[event.keyCode],
|
||||
event.isShiftHold, delegate.state.type == .ofAssociates,
|
||||
let newEvent = event.reinitiate(modifierFlags: [], characters: newChar)
|
||||
{
|
||||
if #available(macOS 10.14, *) {
|
||||
imkCandidates.handleKeyboardEvent(newEvent)
|
||||
} else {
|
||||
imkCandidates.interpretKeyEvents([newEvent])
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
return imkCandidatesEventSubHandler(event: event)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
private func imkCandidatesEventSubHandler(event: NSEvent) -> Bool {
|
||||
guard let delegate = delegate else { return false }
|
||||
let eventArray = [event]
|
||||
guard let imkC = delegate.candidateController() as? CtlCandidateIMK else { return false }
|
||||
if event.isEsc || event.isBackSpace || event.isDelete || (event.isShiftHold && !event.isSpace) {
|
||||
return commonEventHandler(event)
|
||||
} else if event.isSymbolMenuPhysicalKey {
|
||||
// 符號鍵的行為是固定的,不受偏好設定影響。
|
||||
switch imkC.currentLayout {
|
||||
case .horizontal: _ = event.isShiftHold ? imkC.moveUp(self) : imkC.moveDown(self)
|
||||
case .vertical: _ = event.isShiftHold ? imkC.moveLeft(self) : imkC.moveRight(self)
|
||||
@unknown default: break
|
||||
}
|
||||
return true
|
||||
} else if event.isSpace {
|
||||
switch prefs.specifyShiftSpaceKeyBehavior {
|
||||
case true: _ = event.isShiftHold ? imkC.highlightNextCandidate() : imkC.showNextPage()
|
||||
case false: _ = event.isShiftHold ? imkC.showNextPage() : imkC.highlightNextCandidate()
|
||||
}
|
||||
return true
|
||||
} else if event.isTab {
|
||||
switch prefs.specifyShiftTabKeyBehavior {
|
||||
case true: _ = event.isShiftHold ? imkC.showPreviousPage() : imkC.showNextPage()
|
||||
case false: _ = event.isShiftHold ? imkC.highlightPreviousCandidate() : imkC.highlightNextCandidate()
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
if let newChar = CtlCandidateIMK.defaultIMKSelectionKey[event.keyCode] {
|
||||
/// 根據 KeyCode 重新換算一下選字鍵的 NSEvent,糾正其 Character 數值。
|
||||
/// 反正 IMK 選字窗目前也沒辦法修改選字鍵。
|
||||
let newEvent = event.reinitiate(characters: newChar)
|
||||
if let newEvent = newEvent {
|
||||
if prefs.useSCPCTypingMode, delegate.state.type == .ofAssociates {
|
||||
// 註:input.isShiftHold 已經在 Self.handle() 內處理,因為在那邊處理才有效。
|
||||
return event.isShiftHold ? true : commonEventHandler(event)
|
||||
} else {
|
||||
if #available(macOS 10.14, *) {
|
||||
imkC.handleKeyboardEvent(newEvent)
|
||||
} else {
|
||||
imkC.interpretKeyEvents([newEvent])
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if prefs.useSCPCTypingMode, !event.isReservedKey {
|
||||
return commonEventHandler(event)
|
||||
}
|
||||
|
||||
if delegate.state.type == .ofAssociates,
|
||||
!event.isPageUp, !event.isPageDown, !event.isCursorForward, !event.isCursorBackward,
|
||||
!event.isCursorClockLeft, !event.isCursorClockRight, !event.isSpace,
|
||||
!event.isEnter || !prefs.alsoConfirmAssociatedCandidatesByEnter
|
||||
{
|
||||
return commonEventHandler(event)
|
||||
}
|
||||
imkC.interpretKeyEvents(eventArray)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,6 +16,7 @@ import Shared
|
|||
|
||||
extension InputHandler {
|
||||
/// 對於輸入訊號的第一關處理均藉由此函式來進行。
|
||||
/// - Remark: 送入該函式處理之前,先用 inputHandler.handleEvent() 分診、來判斷是否需要交給 IMKCandidates 處理。
|
||||
/// - Parameters:
|
||||
/// - input: 輸入訊號。
|
||||
/// - state: 給定狀態(通常為當前狀態)。
|
||||
|
@ -29,7 +30,7 @@ extension InputHandler {
|
|||
errorCallback: @escaping (String) -> Void
|
||||
) -> Bool {
|
||||
// 如果按鍵訊號內的 inputTest 是空的話,則忽略該按鍵輸入,因為很可能是功能修飾鍵。
|
||||
guard !input.text.isEmpty else { return false }
|
||||
guard !input.text.isEmpty, input.charCode.isPrintable else { return false }
|
||||
|
||||
let inputText: String = input.text
|
||||
var state = state // 常數轉變數。
|
||||
|
|
|
@ -130,145 +130,9 @@ extension SessionCtl {
|
|||
// 準備修飾鍵,用來判定要新增的詞彙是否需要賦以非常低的權重。
|
||||
Self.areWeNerfing = eventToDeal.modifierFlags.contains([.shift, .command])
|
||||
|
||||
// IMK 選字窗處理,當且僅當啟用了 IMK 選字窗的時候才會生效。
|
||||
if let result = imkCandidatesEventPreHandler(event: eventToDeal) {
|
||||
if shouldUseShiftToggleHandle { rencentKeyHandledByInputHandlerEtc = result }
|
||||
return result
|
||||
}
|
||||
|
||||
/// 剩下的 NSEvent 直接交給 commonEventHandler 來處理。
|
||||
/// 這樣可以與 IMK 選字窗共用按鍵處理資源,維護起來也比較方便。
|
||||
let result = commonEventHandler(eventToDeal)
|
||||
if shouldUseShiftToggleHandle {
|
||||
rencentKeyHandledByInputHandlerEtc = result
|
||||
}
|
||||
/// 直接交給 commonEventHandler 來處理。
|
||||
let result = inputHandler.handleEvent(eventToDeal)
|
||||
if shouldUseShiftToggleHandle { rencentKeyHandledByInputHandlerEtc = result }
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private functions
|
||||
|
||||
extension SessionCtl {
|
||||
/// 完成 handle() 函式本該完成的內容,但去掉了與 IMK 選字窗有關的判斷語句。
|
||||
/// 這樣分開處理很有必要,不然 handle() 函式會陷入無限迴圈。
|
||||
/// - Parameter event: 由 IMK 選字窗接收的裝置操作輸入事件。
|
||||
/// - Returns: 回「`true`」以將該案件已攔截處理的訊息傳遞給 IMK;回「`false`」則放行、不作處理。
|
||||
private func commonEventHandler(_ event: NSEvent) -> Bool {
|
||||
// 無法列印的訊號輸入,一概不作處理。
|
||||
// 這個過程不能放在 InputHandler 內,否則不會起作用。
|
||||
if !event.charCode.isPrintable { return false }
|
||||
|
||||
/// 將按鍵行為與當前輸入法狀態結合起來、交給按鍵調度模組來處理。
|
||||
/// 再根據返回的 result bool 數值來告知 IMK「這個按鍵事件是被處理了還是被放行了」。
|
||||
/// 這裡不用 inputHandler.handleCandidate() 是因為需要針對聯想詞輸入狀態做額外處理。
|
||||
let result = inputHandler.handleInput(event: event, state: state) { newState in
|
||||
self.handle(state: newState)
|
||||
} errorCallback: { errorString in
|
||||
vCLog(errorString)
|
||||
IMEApp.buzz()
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/// 完成 handle() 函式本該完成的內容,但專門處理與 IMK 選字窗有關的判斷語句。
|
||||
/// 這樣分開處理很有必要,不然 handle() 函式會陷入無限迴圈。
|
||||
/// - Parameter event: 由 IMK 選字窗接收的裝置操作輸入事件。
|
||||
/// - Returns: 回「`true`」以將該案件已攔截處理的訊息傳遞給 IMK;回「`false`」則放行、不作處理。
|
||||
private func imkCandidatesEventPreHandler(event eventToDeal: NSEvent) -> Bool? {
|
||||
// IMK 選字窗處理,當且僅當啟用了 IMK 選字窗的時候才會生效。
|
||||
// 這樣可以讓 interpretKeyEvents() 函式自行判斷:
|
||||
// - 是就地交給 imkCandidates.interpretKeyEvents() 處理?
|
||||
// - 還是藉由 delegate 扔回 SessionCtl 給 InputHandler 處理?
|
||||
if let imkCandidates = ctlCandidateCurrent as? CtlCandidateIMK, imkCandidates.visible {
|
||||
let event: NSEvent = CtlCandidateIMK.replaceNumPadKeyCodes(target: eventToDeal) ?? eventToDeal
|
||||
|
||||
// Shift+Enter 是個特殊情形,不提前攔截處理的話、會有垃圾參數傳給 delegate 的 inputHandler 從而崩潰。
|
||||
// 所以這裡直接將 Shift Flags 清空。
|
||||
if event.isShiftHold, event.isEnter {
|
||||
guard let newEvent = event.reinitiate(modifierFlags: []) else {
|
||||
NSSound.beep()
|
||||
return true
|
||||
}
|
||||
|
||||
return imkCandidatesEventSubHandler(event: newEvent)
|
||||
}
|
||||
|
||||
// 聯想詞選字。
|
||||
if let newChar = CtlCandidateIMK.defaultIMKSelectionKey[event.keyCode],
|
||||
event.isShiftHold, state.type == .ofAssociates,
|
||||
let newEvent = event.reinitiate(modifierFlags: [], characters: newChar)
|
||||
{
|
||||
if #available(macOS 10.14, *) {
|
||||
imkCandidates.handleKeyboardEvent(newEvent)
|
||||
} else {
|
||||
imkCandidates.interpretKeyEvents([newEvent])
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
return imkCandidatesEventSubHandler(event: event)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
private func imkCandidatesEventSubHandler(event: NSEvent) -> Bool {
|
||||
let eventArray = [event]
|
||||
guard let imkC = ctlCandidateCurrent as? CtlCandidateIMK else { return false }
|
||||
if event.isEsc || event.isBackSpace || event.isDelete || (event.isShiftHold && !event.isSpace) {
|
||||
return commonEventHandler(event)
|
||||
} else if event.isSymbolMenuPhysicalKey {
|
||||
// 符號鍵的行為是固定的,不受偏好設定影響。
|
||||
switch imkC.currentLayout {
|
||||
case .horizontal: _ = event.isShiftHold ? imkC.moveUp(self) : imkC.moveDown(self)
|
||||
case .vertical: _ = event.isShiftHold ? imkC.moveLeft(self) : imkC.moveRight(self)
|
||||
@unknown default: break
|
||||
}
|
||||
return true
|
||||
} else if event.isSpace {
|
||||
switch PrefMgr.shared.specifyShiftSpaceKeyBehavior {
|
||||
case true: _ = event.isShiftHold ? imkC.highlightNextCandidate() : imkC.showNextPage()
|
||||
case false: _ = event.isShiftHold ? imkC.showNextPage() : imkC.highlightNextCandidate()
|
||||
}
|
||||
return true
|
||||
} else if event.isTab {
|
||||
switch PrefMgr.shared.specifyShiftTabKeyBehavior {
|
||||
case true: _ = event.isShiftHold ? imkC.showPreviousPage() : imkC.showNextPage()
|
||||
case false: _ = event.isShiftHold ? imkC.highlightPreviousCandidate() : imkC.highlightNextCandidate()
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
if let newChar = CtlCandidateIMK.defaultIMKSelectionKey[event.keyCode] {
|
||||
/// 根據 KeyCode 重新換算一下選字鍵的 NSEvent,糾正其 Character 數值。
|
||||
/// 反正 IMK 選字窗目前也沒辦法修改選字鍵。
|
||||
let newEvent = event.reinitiate(characters: newChar)
|
||||
if let newEvent = newEvent {
|
||||
if PrefMgr.shared.useSCPCTypingMode, state.type == .ofAssociates {
|
||||
// 註:input.isShiftHold 已經在 Self.handle() 內處理,因為在那邊處理才有效。
|
||||
return event.isShiftHold ? true : commonEventHandler(event)
|
||||
} else {
|
||||
if #available(macOS 10.14, *) {
|
||||
imkC.handleKeyboardEvent(newEvent)
|
||||
} else {
|
||||
imkC.interpretKeyEvents([newEvent])
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if PrefMgr.shared.useSCPCTypingMode, !event.isReservedKey {
|
||||
return commonEventHandler(event)
|
||||
}
|
||||
|
||||
if state.type == .ofAssociates,
|
||||
!event.isPageUp, !event.isPageDown, !event.isCursorForward, !event.isCursorBackward,
|
||||
!event.isCursorClockLeft, !event.isCursorClockRight, !event.isSpace,
|
||||
!event.isEnter || !PrefMgr.shared.alsoConfirmAssociatedCandidatesByEnter
|
||||
{
|
||||
return commonEventHandler(event)
|
||||
}
|
||||
imkC.interpretKeyEvents(eventArray)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -84,6 +84,7 @@
|
|||
5BDB7A4528D4824A001AC277 /* ShiftKeyUpChecker in Frameworks */ = {isa = PBXBuildFile; productRef = 5BDB7A4428D4824A001AC277 /* ShiftKeyUpChecker */; };
|
||||
5BDB7A4728D4824A001AC277 /* Tekkon in Frameworks */ = {isa = PBXBuildFile; productRef = 5BDB7A4628D4824A001AC277 /* Tekkon */; };
|
||||
5BDCBB2E27B4E67A00D0CC59 /* vChewingPhraseEditor.app in Resources */ = {isa = PBXBuildFile; fileRef = 5BD05BB827B2A429004C4F1D /* vChewingPhraseEditor.app */; };
|
||||
5BE1F8A928F86AB5006C7FF5 /* InputHandler_HandleEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BE1F8A828F86AB5006C7FF5 /* InputHandler_HandleEvent.swift */; };
|
||||
5BE377A0288FED8D0037365B /* InputHandler_HandleComposition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BE3779F288FED8D0037365B /* InputHandler_HandleComposition.swift */; };
|
||||
5BE78BD927B3775B005EA1BE /* CtlAboutWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BE78BD827B37750005EA1BE /* CtlAboutWindow.swift */; };
|
||||
5BE78BDD27B3776D005EA1BE /* frmAboutWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5BE78BDA27B37764005EA1BE /* frmAboutWindow.xib */; };
|
||||
|
@ -283,6 +284,7 @@
|
|||
5BDCBB4A27B4F6C700D0CC59 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||
5BDCBB4B27B4F6C700D0CC59 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/frmAboutWindow.strings"; sourceTree = "<group>"; };
|
||||
5BDCBB4D27B4F6C700D0CC59 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
||||
5BE1F8A828F86AB5006C7FF5 /* InputHandler_HandleEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputHandler_HandleEvent.swift; sourceTree = "<group>"; };
|
||||
5BE3779F288FED8D0037365B /* InputHandler_HandleComposition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputHandler_HandleComposition.swift; sourceTree = "<group>"; };
|
||||
5BE78BD827B37750005EA1BE /* CtlAboutWindow.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = CtlAboutWindow.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
|
||||
5BE78BDB27B37764005EA1BE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/frmAboutWindow.xib; sourceTree = "<group>"; };
|
||||
|
@ -675,6 +677,7 @@
|
|||
5BD0113C2818543900609769 /* InputHandler_Core.swift */,
|
||||
5B782EC3280C243C007276DE /* InputHandler_HandleCandidate.swift */,
|
||||
5BE3779F288FED8D0037365B /* InputHandler_HandleComposition.swift */,
|
||||
5BE1F8A828F86AB5006C7FF5 /* InputHandler_HandleEvent.swift */,
|
||||
5B7F225C2808501000DDD3CB /* InputHandler_HandleInput.swift */,
|
||||
5B3133BE280B229700A4A505 /* InputHandler_States.swift */,
|
||||
5BAEFACF28012565001F42C9 /* LMMgr.swift */,
|
||||
|
@ -1081,6 +1084,7 @@
|
|||
5BD0113D2818543900609769 /* InputHandler_Core.swift in Sources */,
|
||||
5BF56F9A28C39D1800DD6839 /* IMEStateData.swift in Sources */,
|
||||
5B21176C287539BB000443A9 /* SessionCtl_HandleStates.swift in Sources */,
|
||||
5BE1F8A928F86AB5006C7FF5 /* InputHandler_HandleEvent.swift in Sources */,
|
||||
5BAEFAD028012565001F42C9 /* LMMgr.swift in Sources */,
|
||||
5B782EC4280C243C007276DE /* InputHandler_HandleCandidate.swift in Sources */,
|
||||
5BA9FD0F27FEDB6B002DE248 /* VwrPrefPaneGeneral.swift in Sources */,
|
||||
|
|
Loading…
Reference in New Issue