Adds a new file KeyHandler.swift to start to break input controller to testable modules.
This commit is contained in:
parent
177cba5d56
commit
82a916c433
|
@ -52,6 +52,7 @@
|
||||||
D44FB74727919D35003C80A6 /* EmacsKeyHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D44FB74627919C83003C80A6 /* EmacsKeyHelper.swift */; };
|
D44FB74727919D35003C80A6 /* EmacsKeyHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D44FB74627919C83003C80A6 /* EmacsKeyHelper.swift */; };
|
||||||
D44FB74A2791B829003C80A6 /* VXHanConvert in Frameworks */ = {isa = PBXBuildFile; productRef = D44FB7492791B829003C80A6 /* VXHanConvert */; };
|
D44FB74A2791B829003C80A6 /* VXHanConvert in Frameworks */ = {isa = PBXBuildFile; productRef = D44FB7492791B829003C80A6 /* VXHanConvert */; };
|
||||||
D44FB74D2792189A003C80A6 /* PhraseReplacementMap.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D44FB74B2792189A003C80A6 /* PhraseReplacementMap.cpp */; };
|
D44FB74D2792189A003C80A6 /* PhraseReplacementMap.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D44FB74B2792189A003C80A6 /* PhraseReplacementMap.cpp */; };
|
||||||
|
D456576E279E4F7B00DF6BC9 /* KeyHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D456576D279E4F7B00DF6BC9 /* KeyHandler.swift */; };
|
||||||
D461B792279DAC010070E734 /* InputState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D461B791279DAC010070E734 /* InputState.swift */; };
|
D461B792279DAC010070E734 /* InputState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D461B791279DAC010070E734 /* InputState.swift */; };
|
||||||
D47B92C027972AD100458394 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47B92BF27972AC800458394 /* main.swift */; };
|
D47B92C027972AD100458394 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47B92BF27972AC800458394 /* main.swift */; };
|
||||||
D47F7DCE278BFB57002F9DD7 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DCD278BFB57002F9DD7 /* PreferencesWindowController.swift */; };
|
D47F7DCE278BFB57002F9DD7 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DCD278BFB57002F9DD7 /* PreferencesWindowController.swift */; };
|
||||||
|
@ -205,6 +206,7 @@
|
||||||
D44FB7482791B346003C80A6 /* VXHanConvert */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = VXHanConvert; path = Packages/VXHanConvert; sourceTree = "<group>"; };
|
D44FB7482791B346003C80A6 /* VXHanConvert */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = VXHanConvert; path = Packages/VXHanConvert; sourceTree = "<group>"; };
|
||||||
D44FB74B2792189A003C80A6 /* PhraseReplacementMap.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = PhraseReplacementMap.cpp; sourceTree = "<group>"; };
|
D44FB74B2792189A003C80A6 /* PhraseReplacementMap.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = PhraseReplacementMap.cpp; sourceTree = "<group>"; };
|
||||||
D44FB74C2792189A003C80A6 /* PhraseReplacementMap.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PhraseReplacementMap.h; sourceTree = "<group>"; };
|
D44FB74C2792189A003C80A6 /* PhraseReplacementMap.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PhraseReplacementMap.h; sourceTree = "<group>"; };
|
||||||
|
D456576D279E4F7B00DF6BC9 /* KeyHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyHandler.swift; sourceTree = "<group>"; };
|
||||||
D461B791279DAC010070E734 /* InputState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputState.swift; sourceTree = "<group>"; };
|
D461B791279DAC010070E734 /* InputState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputState.swift; sourceTree = "<group>"; };
|
||||||
D47B92BF27972AC800458394 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
|
D47B92BF27972AC800458394 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
|
||||||
D47F7DCD278BFB57002F9DD7 /* PreferencesWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = "<group>"; };
|
D47F7DCD278BFB57002F9DD7 /* PreferencesWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = "<group>"; };
|
||||||
|
@ -302,6 +304,7 @@
|
||||||
D41355D6278D7409005E5CBD /* LanguageModelManager.h */,
|
D41355D6278D7409005E5CBD /* LanguageModelManager.h */,
|
||||||
D41355D7278D7409005E5CBD /* LanguageModelManager.mm */,
|
D41355D7278D7409005E5CBD /* LanguageModelManager.mm */,
|
||||||
D461B791279DAC010070E734 /* InputState.swift */,
|
D461B791279DAC010070E734 /* InputState.swift */,
|
||||||
|
D456576D279E4F7B00DF6BC9 /* KeyHandler.swift */,
|
||||||
D47B92BF27972AC800458394 /* main.swift */,
|
D47B92BF27972AC800458394 /* main.swift */,
|
||||||
D427F76B278CA1BA004A2160 /* AppDelegate.swift */,
|
D427F76B278CA1BA004A2160 /* AppDelegate.swift */,
|
||||||
D44FB74427915555003C80A6 /* Preferences.swift */,
|
D44FB74427915555003C80A6 /* Preferences.swift */,
|
||||||
|
@ -705,6 +708,7 @@
|
||||||
D44FB74D2792189A003C80A6 /* PhraseReplacementMap.cpp in Sources */,
|
D44FB74D2792189A003C80A6 /* PhraseReplacementMap.cpp in Sources */,
|
||||||
D44FB74527915565003C80A6 /* Preferences.swift in Sources */,
|
D44FB74527915565003C80A6 /* Preferences.swift in Sources */,
|
||||||
D47F7DD0278C0897002F9DD7 /* NonModalAlertWindowController.swift in Sources */,
|
D47F7DD0278C0897002F9DD7 /* NonModalAlertWindowController.swift in Sources */,
|
||||||
|
D456576E279E4F7B00DF6BC9 /* KeyHandler.swift in Sources */,
|
||||||
D47F7DCE278BFB57002F9DD7 /* PreferencesWindowController.swift in Sources */,
|
D47F7DCE278BFB57002F9DD7 /* PreferencesWindowController.swift in Sources */,
|
||||||
6A0D4ED215FC0D6400ABF4B3 /* InputMethodController.mm in Sources */,
|
6A0D4ED215FC0D6400ABF4B3 /* InputMethodController.mm in Sources */,
|
||||||
D41355DB278E6D17005E5CBD /* McBopomofoLM.cpp in Sources */,
|
D41355DB278E6D17005E5CBD /* McBopomofoLM.cpp in Sources */,
|
||||||
|
|
|
@ -631,7 +631,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
||||||
&& (flags & NSEventModifierFlagShift)) {
|
&& (flags & NSEventModifierFlagShift)) {
|
||||||
NSUInteger index = currentState.markerIndex;
|
NSUInteger index = currentState.markerIndex;
|
||||||
if (index < currentState.composingBuffer.length) {
|
if (index < currentState.composingBuffer.length) {
|
||||||
index -= 1;
|
index += 1;
|
||||||
InputStateMarking *state = [[InputStateMarking alloc] initWithComposingBuffer:currentState.composingBuffer cursorIndex:currentState.cursorIndex markerIndex:index];
|
InputStateMarking *state = [[InputStateMarking alloc] initWithComposingBuffer:currentState.composingBuffer cursorIndex:currentState.cursorIndex markerIndex:index];
|
||||||
[self handleState:state client:client];
|
[self handleState:state client:client];
|
||||||
}
|
}
|
||||||
|
@ -653,7 +653,8 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
||||||
// update the composing buffer
|
// update the composing buffer
|
||||||
composeReading = _bpmfReadingBuffer->hasToneMarker();
|
composeReading = _bpmfReadingBuffer->hasToneMarker();
|
||||||
if (!composeReading) {
|
if (!composeReading) {
|
||||||
[self handleState:_state client:client];
|
InputStateInputting *inputting = [self buildInputingState];
|
||||||
|
[self handleState:inputting client:client];
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,200 @@
|
||||||
|
import Cocoa
|
||||||
|
|
||||||
|
@objc enum KeyCode: UInt16 {
|
||||||
|
case none = 0
|
||||||
|
case enter = 76
|
||||||
|
case up = 126
|
||||||
|
case down = 125
|
||||||
|
case left = 123
|
||||||
|
case right = 124
|
||||||
|
case pageUp = 116
|
||||||
|
case pageDown = 121
|
||||||
|
case home = 115
|
||||||
|
case end = 119
|
||||||
|
case delete = 117
|
||||||
|
}
|
||||||
|
|
||||||
|
class KeyHandlerInput: NSObject {
|
||||||
|
private (set) var event: NSEvent
|
||||||
|
private (set) var useVerticalMode: Bool
|
||||||
|
|
||||||
|
var inputText: String? {
|
||||||
|
event.characters
|
||||||
|
}
|
||||||
|
|
||||||
|
var charCode: UInt16 {
|
||||||
|
guard let inputText = inputText, inputText.count > 0 else {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
let first = inputText[inputText.startIndex].utf16.first!
|
||||||
|
return first
|
||||||
|
}
|
||||||
|
|
||||||
|
var keyCode: UInt16 {
|
||||||
|
event.keyCode
|
||||||
|
}
|
||||||
|
|
||||||
|
var flags: NSEvent.ModifierFlags {
|
||||||
|
event.modifierFlags
|
||||||
|
}
|
||||||
|
|
||||||
|
var cursorForwardKey: KeyCode {
|
||||||
|
useVerticalMode ? .down : .right
|
||||||
|
}
|
||||||
|
|
||||||
|
var cursorBackwardKey: KeyCode {
|
||||||
|
useVerticalMode ? .up : .left
|
||||||
|
}
|
||||||
|
|
||||||
|
var extraChooseCandidateKey: KeyCode {
|
||||||
|
useVerticalMode ? .left : .down
|
||||||
|
}
|
||||||
|
|
||||||
|
var absorbedArrowKey: KeyCode {
|
||||||
|
useVerticalMode ? .right : .up
|
||||||
|
}
|
||||||
|
|
||||||
|
var verticalModeOnlyChooseCandidateKey: KeyCode {
|
||||||
|
useVerticalMode ? absorbedArrowKey : .none
|
||||||
|
}
|
||||||
|
|
||||||
|
init(event: NSEvent, isVerticalMode: Bool) {
|
||||||
|
self.event = event
|
||||||
|
self.useVerticalMode = isVerticalMode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
typealias KeyHandlerStateCallback = (InputState) -> ()
|
||||||
|
typealias KeyHandlerErrorCallback = () -> ()
|
||||||
|
|
||||||
|
@objc protocol KeyHandlerDelegate: AnyObject {
|
||||||
|
func keyHandlerRequestCurrentInputtingState(_ handler: KeyHandler) -> InputStateInputting
|
||||||
|
func keyHandler(_ handler: KeyHandler, requestWriteUserPhrase state: InputStateMarking ) -> Bool
|
||||||
|
|
||||||
|
func keyHandler(_ handler: KeyHandler, isCharCodeValidBmpfReading charCode:UInt16 ) -> Bool
|
||||||
|
func keyHandler(_ handler: KeyHandler, insertCharCodeToBmpfReading charCode:UInt16 ) -> Void
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class KeyHandler: NSObject {
|
||||||
|
var delegate: KeyHandlerDelegate?
|
||||||
|
|
||||||
|
@objc func handle(_ input: KeyHandlerInput,
|
||||||
|
currentState: InputState,
|
||||||
|
stateCallback: @escaping KeyHandlerStateCallback,
|
||||||
|
errorCallback: @escaping KeyHandlerErrorCallback
|
||||||
|
) -> Bool {
|
||||||
|
guard let delegate = delegate else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the inputText is empty, it's a function key combination, we ignore it
|
||||||
|
guard let inputText = input.inputText else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if inputText.isEmpty {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
let flags = input.flags
|
||||||
|
let charCode = input.charCode
|
||||||
|
let keyCode = input.keyCode
|
||||||
|
let emacsKey = EmacsKeyHelper.detect(charCode: charCode, flags: flags)
|
||||||
|
|
||||||
|
// if the composing buffer is empty and there's no reading, and there is some function key combination, we ignore it
|
||||||
|
let isFunctionKey = flags.contains(.command) || flags.contains(.control) || flags.contains(.option) || flags.contains(.numericPad)
|
||||||
|
if currentState is InputStateInputting == false && isFunctionKey {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Caps Lock processing : if Caps Lock is on, temporarily disable bopomofo.
|
||||||
|
if charCode == 8 ||
|
||||||
|
charCode == 13 ||
|
||||||
|
keyCode == input.absorbedArrowKey.rawValue ||
|
||||||
|
keyCode == input.cursorForwardKey.rawValue ||
|
||||||
|
keyCode == input.cursorBackwardKey.rawValue {
|
||||||
|
// do nothing if backspace is pressed -- we ignore the key
|
||||||
|
} else if flags.contains(.capsLock) {
|
||||||
|
// process all possible combination, we hope.
|
||||||
|
stateCallback(InputStateEmpty())
|
||||||
|
|
||||||
|
// first commit everything in the buffer.
|
||||||
|
if flags.contains(.shift) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// if ASCII but not printable, don't use insertText:replacementRange: as many apps don't handle non-ASCII char insertions.
|
||||||
|
if charCode < 0x80 && isprint(Int32(charCode)) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
stateCallback(InputStateCommitting(poppedText: inputText.lowercased()))
|
||||||
|
stateCallback(InputStateEmpty())
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if flags.contains(.numericPad) {
|
||||||
|
if keyCode != KeyCode.left.rawValue &&
|
||||||
|
keyCode != KeyCode.right.rawValue &&
|
||||||
|
keyCode != KeyCode.down.rawValue &&
|
||||||
|
keyCode != KeyCode.up.rawValue &&
|
||||||
|
charCode != 32 &&
|
||||||
|
isprint(Int32(charCode)) != 0 {
|
||||||
|
stateCallback(InputStateEmpty())
|
||||||
|
stateCallback(InputStateCommitting(poppedText: inputText.lowercased()))
|
||||||
|
stateCallback(InputStateEmpty())
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// candidates?
|
||||||
|
|
||||||
|
if let state = currentState as? InputStateMarking {
|
||||||
|
// ESC
|
||||||
|
if charCode == 27 {
|
||||||
|
let inputting = InputStateInputting(composingBuffer: state.composingBuffer, cursorIndex: state.cursorIndex)
|
||||||
|
stateCallback(inputting)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// Enter
|
||||||
|
if charCode == 13 {
|
||||||
|
if delegate.keyHandler(self, requestWriteUserPhrase: state) == false {
|
||||||
|
errorCallback()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
let inputting = InputStateInputting(composingBuffer: state.composingBuffer, cursorIndex: state.cursorIndex)
|
||||||
|
stateCallback(inputting)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// Shift + left
|
||||||
|
if (keyCode == input.cursorBackwardKey.rawValue || emacsKey == .backward) && flags.contains(.shift) {
|
||||||
|
var index = state.markerIndex
|
||||||
|
if index > 0 {
|
||||||
|
index -= 1
|
||||||
|
let marking = InputStateMarking(composingBuffer: state.composingBuffer, cursorIndex: state.cursorIndex, markerIndex: state.markerIndex)
|
||||||
|
stateCallback(marking)
|
||||||
|
} else {
|
||||||
|
errorCallback()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (keyCode == input.cursorForwardKey.rawValue || emacsKey == .forward) && flags.contains(.shift) {
|
||||||
|
var index = state.markerIndex
|
||||||
|
if index < state.composingBuffer.count {
|
||||||
|
index += 1
|
||||||
|
let marking = InputStateMarking(composingBuffer: state.composingBuffer, cursorIndex: state.cursorIndex, markerIndex: state.markerIndex)
|
||||||
|
stateCallback(marking)
|
||||||
|
} else {
|
||||||
|
errorCallback()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue