[WIP] Starts to extract input states from the input controller.
This commit is contained in:
parent
43d9e8bd42
commit
177cba5d56
|
@ -52,6 +52,7 @@
|
|||
D44FB74727919D35003C80A6 /* EmacsKeyHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D44FB74627919C83003C80A6 /* EmacsKeyHelper.swift */; };
|
||||
D44FB74A2791B829003C80A6 /* VXHanConvert in Frameworks */ = {isa = PBXBuildFile; productRef = D44FB7492791B829003C80A6 /* VXHanConvert */; };
|
||||
D44FB74D2792189A003C80A6 /* PhraseReplacementMap.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D44FB74B2792189A003C80A6 /* PhraseReplacementMap.cpp */; };
|
||||
D461B792279DAC010070E734 /* InputState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D461B791279DAC010070E734 /* InputState.swift */; };
|
||||
D47B92C027972AD100458394 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47B92BF27972AC800458394 /* main.swift */; };
|
||||
D47F7DCE278BFB57002F9DD7 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DCD278BFB57002F9DD7 /* PreferencesWindowController.swift */; };
|
||||
D47F7DD0278C0897002F9DD7 /* NonModalAlertWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DCF278C0897002F9DD7 /* NonModalAlertWindowController.swift */; };
|
||||
|
@ -204,6 +205,7 @@
|
|||
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>"; };
|
||||
D44FB74C2792189A003C80A6 /* PhraseReplacementMap.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PhraseReplacementMap.h; 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>"; };
|
||||
D47F7DCD278BFB57002F9DD7 /* PreferencesWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = "<group>"; };
|
||||
D47F7DCF278C0897002F9DD7 /* NonModalAlertWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonModalAlertWindowController.swift; sourceTree = "<group>"; };
|
||||
|
@ -299,6 +301,7 @@
|
|||
6A0D4EC715FC0D6400ABF4B3 /* InputMethodController.mm */,
|
||||
D41355D6278D7409005E5CBD /* LanguageModelManager.h */,
|
||||
D41355D7278D7409005E5CBD /* LanguageModelManager.mm */,
|
||||
D461B791279DAC010070E734 /* InputState.swift */,
|
||||
D47B92BF27972AC800458394 /* main.swift */,
|
||||
D427F76B278CA1BA004A2160 /* AppDelegate.swift */,
|
||||
D44FB74427915555003C80A6 /* Preferences.swift */,
|
||||
|
@ -696,6 +699,7 @@
|
|||
files = (
|
||||
D427F76C278CA2B0004A2160 /* AppDelegate.swift in Sources */,
|
||||
6ACC3D442793701600F1B140 /* ParselessPhraseDB.cpp in Sources */,
|
||||
D461B792279DAC010070E734 /* InputState.swift in Sources */,
|
||||
D47B92C027972AD100458394 /* main.swift in Sources */,
|
||||
D44FB74727919D35003C80A6 /* EmacsKeyHelper.swift in Sources */,
|
||||
D44FB74D2792189A003C80A6 /* PhraseReplacementMap.cpp in Sources */,
|
||||
|
|
|
@ -53,8 +53,11 @@ namespace Formosa {
|
|||
void setJoinSeparator(const string& separator);
|
||||
const string joinSeparator() const;
|
||||
|
||||
// TODO: Remove these later.
|
||||
size_t markerCursorIndex() const;
|
||||
// TODO: Remove these later.
|
||||
void setMarkerCursorIndex(size_t inNewIndex);
|
||||
|
||||
vector<string> readingsAtRange(size_t begin, size_t end) const;
|
||||
|
||||
Grid& grid();
|
||||
|
|
|
@ -79,6 +79,12 @@ static inline NSString *LocalizationNotNeeded(NSString *s) {
|
|||
return s;
|
||||
}
|
||||
|
||||
@interface McBopomofoInputMethodController()
|
||||
{
|
||||
InputState *_state;
|
||||
}
|
||||
@end
|
||||
|
||||
@interface McBopomofoInputMethodController (VTCandidateController) <VTCandidateControllerDelegate>
|
||||
@end
|
||||
|
||||
|
@ -115,8 +121,6 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
|
|||
if (_builder) {
|
||||
delete _builder;
|
||||
}
|
||||
// the two client pointers are weak pointers (i.e. we don't retain them)
|
||||
// therefore we don't do anything about it
|
||||
}
|
||||
|
||||
- (id)initWithServer:(IMKServer *)server delegate:(id)delegate client:(id)client
|
||||
|
@ -145,6 +149,7 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
|
|||
_composingBuffer = [[NSMutableString alloc] init];
|
||||
|
||||
_inputMode = kBopomofoModeIdentifier;
|
||||
_state = [[InputStateEmpty alloc] init];
|
||||
}
|
||||
|
||||
return self;
|
||||
|
@ -205,9 +210,8 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
|
|||
// reset the state
|
||||
_currentDeferredClient = nil;
|
||||
_currentCandidateClient = nil;
|
||||
_builder->clear();
|
||||
_walkedNodes.clear();
|
||||
[_composingBuffer setString:@""];
|
||||
InputStateEmpty *newState = [[InputStateEmpty alloc] init];
|
||||
[self handleState:newState client:client];
|
||||
|
||||
// checks and populates the default settings
|
||||
switch (Preferences.keyboardLayout) {
|
||||
|
@ -237,27 +241,13 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
|
|||
_languageModel->setExternalConverterEnabled(Preferences.chineseConversionStyle == 1);
|
||||
|
||||
[(AppDelegate *)[NSApp delegate] checkForUpdate];
|
||||
|
||||
}
|
||||
|
||||
- (void)deactivateServer:(id)client
|
||||
{
|
||||
// clean up reading buffer residues
|
||||
if (!_bpmfReadingBuffer->isEmpty()) {
|
||||
_bpmfReadingBuffer->clear();
|
||||
[client setMarkedText:@"" selectionRange:NSMakeRange(0, 0) replacementRange:NSMakeRange(NSNotFound, NSNotFound)];
|
||||
}
|
||||
|
||||
// commit any residue in the composing buffer
|
||||
[self commitComposition:client];
|
||||
|
||||
_currentDeferredClient = nil;
|
||||
_currentCandidateClient = nil;
|
||||
|
||||
gCurrentCandidateController.delegate = nil;
|
||||
gCurrentCandidateController.visible = NO;
|
||||
[_candidates removeAllObjects];
|
||||
|
||||
[self _hideTooltip];
|
||||
InputStateDeactive *newState = [[InputStateDeactive alloc] init];
|
||||
[self handleState:newState client:client];
|
||||
}
|
||||
|
||||
- (void)setValue:(id)value forTag:(long)tag client:(id)sender
|
||||
|
@ -288,6 +278,13 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
|
|||
_inputMode = newInputMode;
|
||||
_languageModel = newLanguageModel;
|
||||
|
||||
if (_builder) {
|
||||
delete _builder;
|
||||
_builder = new BlockReadingBuilder(_languageModel);
|
||||
_builder->setJoinSeparator("-");
|
||||
}
|
||||
|
||||
|
||||
if (!_bpmfReadingBuffer->isEmpty()) {
|
||||
_bpmfReadingBuffer->clear();
|
||||
[self updateClientComposingBuffer:sender];
|
||||
|
@ -297,11 +294,6 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
|
|||
[self commitComposition:sender];
|
||||
}
|
||||
|
||||
if (_builder) {
|
||||
delete _builder;
|
||||
_builder = new BlockReadingBuilder(_languageModel);
|
||||
_builder->setJoinSeparator("-");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -335,22 +327,34 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
|
|||
return;
|
||||
}
|
||||
|
||||
NSString *buffer = @"";
|
||||
if ([_state isKindOfClass: [InputStateInputting class]] ) {
|
||||
buffer = [(InputStateInputting *)_state composingBuffer];
|
||||
}
|
||||
|
||||
// Chinese conversion.
|
||||
NSString *buffer = [self _convertToSimplifiedChineseIfRequired:_composingBuffer];
|
||||
// NSString *buffer = [self _convertToSimplifiedChineseIfRequired:_composingBuffer];
|
||||
|
||||
// commit the text, clear the state
|
||||
[client insertText:buffer replacementRange:NSMakeRange(NSNotFound, NSNotFound)];
|
||||
_builder->clear();
|
||||
_walkedNodes.clear();
|
||||
[_composingBuffer setString:@""];
|
||||
gCurrentCandidateController.visible = NO;
|
||||
[_candidates removeAllObjects];
|
||||
[self _hideTooltip];
|
||||
if ([buffer length]) {
|
||||
buffer = [self _convertToSimplifiedChineseIfRequired:_composingBuffer];
|
||||
[client insertText:buffer replacementRange:NSMakeRange(NSNotFound, NSNotFound)];
|
||||
}
|
||||
|
||||
// zonble: these should be handled by states.
|
||||
// _builder->clear();
|
||||
// _walkedNodes.clear();
|
||||
// [_composingBuffer setString:@""];
|
||||
// gCurrentCandidateController.visible = NO;
|
||||
// [_candidates removeAllObjects];
|
||||
// [self _hideTooltip];
|
||||
}
|
||||
|
||||
NS_INLINE size_t min(size_t a, size_t b) { return a < b ? a : b; }
|
||||
NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
||||
|
||||
|
||||
|
||||
// TODO: bug #28 is more likely to live in this method.
|
||||
- (void)updateClientComposingBuffer:(id)client
|
||||
{
|
||||
|
@ -480,10 +484,10 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
if (_builder->grid().width() > (size_t)composingBufferSize) {
|
||||
if (_walkedNodes.size() > 0) {
|
||||
NodeAnchor &anchor = _walkedNodes[0];
|
||||
NSString *popedText = [NSString stringWithUTF8String:anchor.node->currentKeyValue().value.c_str()];
|
||||
NSString *poppedText = [NSString stringWithUTF8String:anchor.node->currentKeyValue().value.c_str()];
|
||||
// Chinese conversion.
|
||||
popedText = [self _convertToSimplifiedChineseIfRequired:popedText];
|
||||
[client insertText:popedText replacementRange:NSMakeRange(NSNotFound, NSNotFound)];
|
||||
poppedText = [self _convertToSimplifiedChineseIfRequired:poppedText];
|
||||
[client insertText:poppedText replacementRange:NSMakeRange(NSNotFound, NSNotFound)];
|
||||
_builder->removeHeadReadings(anchor.spanningLength);
|
||||
}
|
||||
}
|
||||
|
@ -542,9 +546,8 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
}
|
||||
|
||||
// if the composing buffer is empty and there's no reading, and there is some function key combination, we ignore it
|
||||
if (![_composingBuffer length] &&
|
||||
_bpmfReadingBuffer->isEmpty() &&
|
||||
((flags & NSEventModifierFlagCommand) || (flags & NSEventModifierFlagControl) || (flags & NSEventModifierFlagOption) || (flags & NSEventModifierFlagNumericPad))) {
|
||||
BOOL isFunctionKey = ((flags & NSEventModifierFlagCommand) || (flags & NSEventModifierFlagControl) || (flags & NSEventModifierFlagOption) || (flags & NSEventModifierFlagNumericPad));
|
||||
if (![_state isKindOfClass:[InputStateInputting class]] && isFunctionKey) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
|
@ -554,9 +557,8 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
}
|
||||
else if (flags & NSAlphaShiftKeyMask) {
|
||||
// process all possible combination, we hope.
|
||||
if ([_composingBuffer length]) {
|
||||
[self commitComposition:client];
|
||||
}
|
||||
InputStateEmpty *emptyState = [[InputStateEmpty alloc] init];
|
||||
[self handleState:emptyState client:client];
|
||||
|
||||
// first commit everything in the buffer.
|
||||
if (flags & NSEventModifierFlagShift) {
|
||||
|
@ -569,75 +571,79 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
}
|
||||
|
||||
// when shift is pressed, don't do further processing, since it outputs capital letter anyway.
|
||||
NSString *popedText = [inputText lowercaseString];
|
||||
[client insertText:popedText replacementRange:NSMakeRange(NSNotFound, NSNotFound)];
|
||||
InputStateCommitting *committingState = [[InputStateCommitting alloc] initWithPoppedText:[inputText lowercaseString]];
|
||||
[self handleState:committingState client:client];
|
||||
[self handleState:emptyState client:client];
|
||||
return YES;
|
||||
}
|
||||
|
||||
if (flags & NSEventModifierFlagNumericPad) {
|
||||
if (keyCode != kLeftKeyCode && keyCode != kRightKeyCode && keyCode != kDownKeyCode && keyCode != kUpKeyCode && charCode != 32 && isprint(charCode)) {
|
||||
if ([_composingBuffer length]) {
|
||||
[self commitComposition:client];
|
||||
}
|
||||
|
||||
NSString *popedText = [inputText lowercaseString];
|
||||
[client insertText:popedText replacementRange:NSMakeRange(NSNotFound, NSNotFound)];
|
||||
InputStateEmpty *emptyState = [[InputStateEmpty alloc] init];
|
||||
[self handleState:emptyState client:client];
|
||||
InputStateCommitting *commiting = [[InputStateCommitting alloc] initWithPoppedText:[inputText lowercaseString]];
|
||||
[self handleState:commiting client:client];
|
||||
[self handleState:emptyState client:client];
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
// if we have candidate, it means we need to pass the event to the candidate handler
|
||||
if ([_candidates count]) {
|
||||
if ([_state isKindOfClass:[InputStateChoosingCandidate class]]) {
|
||||
return [self _handleCandidateEventWithInputText:inputText charCode:charCode keyCode:keyCode emacsKey:(McBopomofoEmacsKey)emacsKey];
|
||||
}
|
||||
|
||||
// If we have marker index.
|
||||
if (_builder->markerCursorIndex() != SIZE_MAX) {
|
||||
// ESC
|
||||
if ([_state isKindOfClass:[InputStateMarking class]]) {
|
||||
InputStateMarking *currentState = (InputStateMarking *)_state;
|
||||
|
||||
if (charCode == 27) {
|
||||
_builder->setMarkerCursorIndex(SIZE_MAX);
|
||||
[self updateClientComposingBuffer:client];
|
||||
InputStateInputting *state = [self buildInputingState];
|
||||
[self handleState:state client:client];
|
||||
return YES;
|
||||
}
|
||||
// Enter
|
||||
if (charCode == 13) {
|
||||
if ([self _writeUserPhrase]) {
|
||||
_builder->setMarkerCursorIndex(SIZE_MAX);
|
||||
}
|
||||
else {
|
||||
if (![self _writeUserPhrase]) {
|
||||
[self beep];
|
||||
}
|
||||
[self updateClientComposingBuffer:client];
|
||||
InputStateInputting *state = [self buildInputingState];
|
||||
[self handleState:state client:client];
|
||||
return YES;
|
||||
}
|
||||
// Shift + left
|
||||
if ((keyCode == cursorBackwardKey || emacsKey == McBopomofoEmacsKeyBackward)
|
||||
&& (flags & NSEventModifierFlagShift)) {
|
||||
if (_builder->markerCursorIndex() > 0) {
|
||||
_builder->setMarkerCursorIndex(_builder->markerCursorIndex() - 1);
|
||||
NSUInteger index = currentState.markerIndex;
|
||||
if (index > 0) {
|
||||
index -= 1;
|
||||
InputStateMarking *state = [[InputStateMarking alloc] initWithComposingBuffer:currentState.composingBuffer cursorIndex:currentState.cursorIndex markerIndex:index];
|
||||
[self handleState:state client:client];
|
||||
}
|
||||
else {
|
||||
[self handleState:currentState client:client];
|
||||
[self beep];
|
||||
}
|
||||
[self updateClientComposingBuffer:client];
|
||||
return YES;
|
||||
}
|
||||
// Shift + Right
|
||||
if ((keyCode == cursorForwardKey || emacsKey == McBopomofoEmacsKeyForward)
|
||||
&& (flags & NSEventModifierFlagShift)) {
|
||||
if (_builder->markerCursorIndex() < _builder->length()) {
|
||||
_builder->setMarkerCursorIndex(_builder->markerCursorIndex() + 1);
|
||||
&& (flags & NSEventModifierFlagShift)) {
|
||||
NSUInteger index = currentState.markerIndex;
|
||||
if (index < currentState.composingBuffer.length) {
|
||||
index -= 1;
|
||||
InputStateMarking *state = [[InputStateMarking alloc] initWithComposingBuffer:currentState.composingBuffer cursorIndex:currentState.cursorIndex markerIndex:index];
|
||||
[self handleState:state client:client];
|
||||
}
|
||||
else {
|
||||
[self handleState:currentState client:client];
|
||||
[self beep];
|
||||
}
|
||||
[self updateClientComposingBuffer:client];
|
||||
return YES;
|
||||
}
|
||||
|
||||
_builder->setMarkerCursorIndex(SIZE_MAX);
|
||||
}
|
||||
|
||||
|
||||
// see if it's valid BPMF reading
|
||||
if (_bpmfReadingBuffer->isValidKey((char)charCode)) {
|
||||
_bpmfReadingBuffer->combineKey((char)charCode);
|
||||
|
@ -647,7 +653,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
// update the composing buffer
|
||||
composeReading = _bpmfReadingBuffer->hasToneMarker();
|
||||
if (!composeReading) {
|
||||
[self updateClientComposingBuffer:client];
|
||||
[self handleState:_state client:client];
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
@ -662,7 +668,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
// see if we have a unigram for this
|
||||
if (!_languageModel->hasUnigramsForKey(reading)) {
|
||||
[self beep];
|
||||
[self updateClientComposingBuffer:client];
|
||||
[self handleState:_state client:client];
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
@ -670,7 +676,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
_builder->insertReadingAtCursor(reading);
|
||||
|
||||
// then walk the lattice
|
||||
[self popOverflowComposingTextAndWalk:client];
|
||||
NSString *poppedText = [self popOverflowComposingTextAndWalk];
|
||||
|
||||
// get user override model suggestion
|
||||
string overrideValue = (_inputMode == kPlainBopomofoModeIdentifier) ? "" :
|
||||
|
@ -685,7 +691,10 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
|
||||
// then update the text
|
||||
_bpmfReadingBuffer->clear();
|
||||
[self updateClientComposingBuffer:client];
|
||||
|
||||
InputStateInputting *state = [self buildInputingState];
|
||||
state.poppedText = poppedText;
|
||||
[self handleState:state client:client];
|
||||
|
||||
if (_inputMode == kPlainBopomofoModeIdentifier) {
|
||||
[self _showCandidateWindowUsingVerticalMode:useVerticalMode client:client];
|
||||
|
@ -701,14 +710,18 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
// if the spacebar is NOT set to be a selection key
|
||||
if ((flags & NSEventModifierFlagShift) != 0 || !Preferences.chooseCandidateUsingSpace) {
|
||||
if (_builder->cursorIndex() >= _builder->length()) {
|
||||
[_composingBuffer appendString:@" "];
|
||||
[self commitComposition:client];
|
||||
_bpmfReadingBuffer->clear();
|
||||
InputStateCommitting *commiting = [[InputStateCommitting alloc] initWithPoppedText:@" "];
|
||||
[self handleState:commiting client:client];
|
||||
InputStateEmpty *empty = [[InputStateEmpty alloc] init];
|
||||
[self handleState:empty client:client];
|
||||
}
|
||||
else if (_languageModel->hasUnigramsForKey(" ")) {
|
||||
_builder->insertReadingAtCursor(" ");
|
||||
[self popOverflowComposingTextAndWalk:client];
|
||||
[self updateClientComposingBuffer:client];
|
||||
NSString *poppedText = [self popOverflowComposingTextAndWalk];
|
||||
InputStateInputting *state = [self buildInputingState];
|
||||
state.poppedText = poppedText;
|
||||
[self handleState:state client:client];
|
||||
}
|
||||
return YES;
|
||||
|
||||
|
@ -725,13 +738,8 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
if (escToClearInputBufferEnabled) {
|
||||
// if the optioon is enabled, we clear everythiong including the composing
|
||||
// buffer, walked nodes and the reading.
|
||||
if (![_composingBuffer length]) {
|
||||
return NO;
|
||||
}
|
||||
_bpmfReadingBuffer->clear();
|
||||
_builder->clear();
|
||||
_walkedNodes.clear();
|
||||
[_composingBuffer setString:@""];
|
||||
InputStateEmpty *empty = [[InputStateEmpty alloc] init];
|
||||
[self handleState:empty client:client];
|
||||
}
|
||||
else {
|
||||
// if reading is not empty, we cancel the reading; Apple's built-in
|
||||
|
@ -742,8 +750,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
|
||||
if (_bpmfReadingBuffer->isEmpty()) {
|
||||
// no nee to beep since the event is deliberately triggered by user
|
||||
|
||||
if (![_composingBuffer length]) {
|
||||
if (![_state isKindOfClass:[InputStateInputting class]]) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
@ -752,7 +759,8 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
}
|
||||
}
|
||||
|
||||
[self updateClientComposingBuffer:client];
|
||||
InputStateInputting *newState = [self buildInputingState];
|
||||
[self handleState:newState client:client];
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
@ -762,7 +770,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
[self beep];
|
||||
}
|
||||
else {
|
||||
if (![_composingBuffer length]) {
|
||||
if (![_state isKindOfClass:[InputStateInputting class]]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
|
@ -784,7 +792,8 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
}
|
||||
}
|
||||
|
||||
[self updateClientComposingBuffer:client];
|
||||
InputStateInputting *newState = [self buildInputingState];
|
||||
[self handleState:newState client:client];
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
@ -794,7 +803,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
[self beep];
|
||||
}
|
||||
else {
|
||||
if (![_composingBuffer length]) {
|
||||
if (![_state isKindOfClass:[InputStateInputting class]]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
|
@ -815,7 +824,8 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
}
|
||||
}
|
||||
|
||||
[self updateClientComposingBuffer:client];
|
||||
InputStateInputting *newState = [self buildInputingState];
|
||||
[self handleState:newState client:client];
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
@ -824,7 +834,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
[self beep];
|
||||
}
|
||||
else {
|
||||
if (![_composingBuffer length]) {
|
||||
if (![_state isKindOfClass:[InputStateInputting class]]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
|
@ -836,7 +846,8 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
}
|
||||
}
|
||||
|
||||
[self updateClientComposingBuffer:client];
|
||||
InputStateInputting *newState = [self buildInputingState];
|
||||
[self handleState:newState client:client];
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
@ -845,7 +856,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
[self beep];
|
||||
}
|
||||
else {
|
||||
if (![_composingBuffer length]) {
|
||||
if (![_state isKindOfClass:[InputStateInputting class]]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
|
@ -857,7 +868,8 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
}
|
||||
}
|
||||
|
||||
[self updateClientComposingBuffer:client];
|
||||
InputStateInputting *newState = [self buildInputingState];
|
||||
[self handleState:newState client:client];
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
@ -865,14 +877,15 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
if (!_bpmfReadingBuffer->isEmpty()) {
|
||||
[self beep];
|
||||
}
|
||||
[self updateClientComposingBuffer:client];
|
||||
InputStateInputting *newState = [self buildInputingState];
|
||||
[self handleState:newState client:client];
|
||||
return YES;
|
||||
}
|
||||
|
||||
// Backspace
|
||||
if (charCode == 8) {
|
||||
if (_bpmfReadingBuffer->isEmpty()) {
|
||||
if (![_composingBuffer length]) {
|
||||
if (![_state isKindOfClass:[InputStateInputting class]]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
|
@ -888,14 +901,15 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
_bpmfReadingBuffer->backspace();
|
||||
}
|
||||
|
||||
[self updateClientComposingBuffer:client];
|
||||
InputStateInputting *newState = [self buildInputingState];
|
||||
[self handleState:newState client:client];
|
||||
return YES;
|
||||
}
|
||||
|
||||
// Delete
|
||||
if (keyCode == kDeleteKeyCode || emacsKey == McBopomofoEmacsKeyDelete) {
|
||||
if (_bpmfReadingBuffer->isEmpty()) {
|
||||
if (![_composingBuffer length]) {
|
||||
if (![_state isKindOfClass:[InputStateInputting class]]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
|
@ -911,32 +925,32 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
[self beep];
|
||||
}
|
||||
|
||||
[self updateClientComposingBuffer:client];
|
||||
InputStateInputting *newState = [self buildInputingState];
|
||||
[self handleState:newState client:client];
|
||||
return YES;
|
||||
}
|
||||
|
||||
// Enter
|
||||
if (charCode == 13) {
|
||||
if (![_composingBuffer length]) {
|
||||
return NO;
|
||||
InputStateInputting *newState = [self buildInputingState];
|
||||
[self handleState:newState client:client];
|
||||
|
||||
if ([_state isKindOfClass:[InputStateInputting class]]) {
|
||||
InputStateInputting *current = (InputStateInputting *)_state;
|
||||
NSString *composingBuffer = current.composingBuffer;
|
||||
InputStateCommitting *commiting = [[InputStateCommitting alloc] initWithPoppedText:composingBuffer];
|
||||
[self handleState:commiting client:client];
|
||||
InputState *empty = [[InputState alloc] init];
|
||||
[self handleState:empty client:client];
|
||||
return YES;
|
||||
}
|
||||
|
||||
[self commitComposition:client];
|
||||
return YES;
|
||||
return NO;
|
||||
}
|
||||
|
||||
// punctuation list
|
||||
if ((char)charCode == '`') {
|
||||
if (_languageModel->hasUnigramsForKey(string("_punctuation_list"))) {
|
||||
if (_bpmfReadingBuffer->isEmpty()) {
|
||||
_builder->insertReadingAtCursor(string("_punctuation_list"));
|
||||
[self popOverflowComposingTextAndWalk:client];
|
||||
[self _showCandidateWindowUsingVerticalMode:useVerticalMode client:client];
|
||||
}
|
||||
else { // If there is still unfinished bpmf reading, ignore the punctuation
|
||||
[self beep];
|
||||
}
|
||||
[self updateClientComposingBuffer:client];
|
||||
if ([self _handlePunctuation:string("_punctuation_list") usingVerticalMode:useVerticalMode client:client]) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
@ -956,11 +970,9 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
}
|
||||
|
||||
if ((char)charCode >= 'A' && (char)charCode <= 'Z') {
|
||||
if ([_composingBuffer length]) {
|
||||
string letter = string("_letter_") + string(1, (char)charCode);
|
||||
if ([self _handlePunctuation:letter usingVerticalMode:useVerticalMode client:client]) {
|
||||
return YES;
|
||||
}
|
||||
string letter = string("_letter_") + string(1, (char)charCode);
|
||||
if ([self _handlePunctuation:letter usingVerticalMode:useVerticalMode client:client]) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -969,7 +981,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
// actually consumed)
|
||||
if ([_composingBuffer length] || !_bpmfReadingBuffer->isEmpty()) {
|
||||
[self beep];
|
||||
[self updateClientComposingBuffer:client];
|
||||
[self handleState:_state client:client];
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
@ -979,19 +991,23 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
- (BOOL)_handlePunctuation:(string)customPunctuation usingVerticalMode:(BOOL)useVerticalMode client:(id)client
|
||||
{
|
||||
if (_languageModel->hasUnigramsForKey(customPunctuation)) {
|
||||
NSString *poppedText = @"";
|
||||
if (_bpmfReadingBuffer->isEmpty()) {
|
||||
_builder->insertReadingAtCursor(customPunctuation);
|
||||
[self popOverflowComposingTextAndWalk:client];
|
||||
poppedText = [self popOverflowComposingTextAndWalk];
|
||||
}
|
||||
else { // If there is still unfinished bpmf reading, ignore the punctuation
|
||||
[self beep];
|
||||
}
|
||||
[self updateClientComposingBuffer:client];
|
||||
InputStateInputting *inputting = [self buildInputingState];
|
||||
inputting.poppedText = poppedText;
|
||||
[self handleState:inputting client:client];
|
||||
|
||||
if (_inputMode == kPlainBopomofoModeIdentifier && _bpmfReadingBuffer->isEmpty()) {
|
||||
[self collectCandidates];
|
||||
if ([_candidates count] == 1) {
|
||||
[self commitComposition:client];
|
||||
InputStateEmpty *empty = [[InputStateEmpty alloc] init];
|
||||
[self handleState:empty client:client];
|
||||
}
|
||||
else {
|
||||
[self _showCandidateWindowUsingVerticalMode:useVerticalMode client:client];
|
||||
|
@ -1228,6 +1244,186 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
return [self handleInputText:inputText key:keyCode modifiers:flags client:client];
|
||||
}
|
||||
|
||||
#pragma mark - States
|
||||
|
||||
- (InputStateInputting *)buildInputingState
|
||||
{
|
||||
// "updating the composing buffer" means to request the client to "refresh" the text input buffer
|
||||
// with our "composing text"
|
||||
NSMutableString *composingBuffer = [[NSMutableString alloc] init];
|
||||
NSInteger composedStringCursorIndex = 0;
|
||||
|
||||
size_t readingCursorIndex = 0;
|
||||
size_t builderCursorIndex = _builder->cursorIndex();
|
||||
|
||||
// we must do some Unicode codepoint counting to find the actual cursor location for the client
|
||||
// i.e. we need to take UTF-16 into consideration, for which a surrogate pair takes 2 UniChars
|
||||
// locations
|
||||
for (vector<NodeAnchor>::iterator wi = _walkedNodes.begin(), we = _walkedNodes.end() ; wi != we ; ++wi) {
|
||||
if ((*wi).node) {
|
||||
string nodeStr = (*wi).node->currentKeyValue().value;
|
||||
vector<string> codepoints = OVUTF8Helper::SplitStringByCodePoint(nodeStr);
|
||||
size_t codepointCount = codepoints.size();
|
||||
|
||||
NSString *valueString = [NSString stringWithUTF8String:nodeStr.c_str()];
|
||||
[composingBuffer appendString:valueString];
|
||||
|
||||
// this re-aligns the cursor index in the composed string
|
||||
// (the actual cursor on the screen) with the builder's logical
|
||||
// cursor (reading) cursor; each built node has a "spanning length"
|
||||
// (e.g. two reading blocks has a spanning length of 2), and we
|
||||
// accumulate those lengthes to calculate the displayed cursor
|
||||
// index
|
||||
size_t spanningLength = (*wi).spanningLength;
|
||||
if (readingCursorIndex + spanningLength <= builderCursorIndex) {
|
||||
composedStringCursorIndex += [valueString length];
|
||||
readingCursorIndex += spanningLength;
|
||||
}
|
||||
else {
|
||||
for (size_t i = 0; i < codepointCount && readingCursorIndex < builderCursorIndex; i++) {
|
||||
composedStringCursorIndex += [[NSString stringWithUTF8String:codepoints[i].c_str()] length];
|
||||
readingCursorIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// now we gather all the info, we separate the composing buffer to two parts, head and tail,
|
||||
// and insert the reading text (the Mandarin syllable) in between them;
|
||||
// the reading text is what the user is typing
|
||||
NSString *head = [composingBuffer substringToIndex:composedStringCursorIndex];
|
||||
NSString *reading = [NSString stringWithUTF8String:_bpmfReadingBuffer->composedString().c_str()];
|
||||
NSString *tail = [composingBuffer substringFromIndex:composedStringCursorIndex];
|
||||
NSString *composedText = [head stringByAppendingString:[reading stringByAppendingString:tail]];
|
||||
NSInteger cursorIndex = composedStringCursorIndex + [reading length];
|
||||
|
||||
InputStateInputting *newState = [[InputStateInputting alloc] initWithComposingBuffer:composedText cursorIndex:cursorIndex];
|
||||
return newState;
|
||||
}
|
||||
|
||||
- (NSString *)popOverflowComposingTextAndWalk
|
||||
{
|
||||
// in an ideal world, we can as well let the user type forever,
|
||||
// but because the Viterbi algorithm has a complexity of O(N^2),
|
||||
// the walk will become slower as the number of nodes increase,
|
||||
// therefore we need to "pop out" overflown text -- they usually
|
||||
// lose their influence over the whole MLE anyway -- so tht when
|
||||
// the user type along, the already composed text at front will
|
||||
// be popped out
|
||||
|
||||
NSString *poppedText = @"";
|
||||
NSInteger composingBufferSize = Preferences.composingBufferSize;
|
||||
|
||||
if (_builder->grid().width() > (size_t)composingBufferSize) {
|
||||
if (_walkedNodes.size() > 0) {
|
||||
NodeAnchor &anchor = _walkedNodes[0];
|
||||
poppedText = [NSString stringWithUTF8String:anchor.node->currentKeyValue().value.c_str()];
|
||||
// Chinese conversion.
|
||||
poppedText = [self _convertToSimplifiedChineseIfRequired:poppedText];
|
||||
_builder->removeHeadReadings(anchor.spanningLength);
|
||||
}
|
||||
}
|
||||
|
||||
[self walk];
|
||||
return poppedText;
|
||||
}
|
||||
|
||||
- (void)commitCurrentTextIfRequired:(id)client
|
||||
{
|
||||
|
||||
// if it's Terminal, we don't commit at the first call (the client of which will not be IPMDServerClientWrapper)
|
||||
// then we defer the update in the next runloop round -- so that the composing buffer is not
|
||||
// meaninglessly flushed, an annoying bug in Terminal.app since Mac OS X 10.5
|
||||
// if ([[client bundleIdentifier] isEqualToString:@"com.apple.Terminal"] && ![NSStringFromClass([client class]) isEqualToString:@"IPMDServerClientWrapper"]) {
|
||||
// if (_currentDeferredClient) {
|
||||
// [self performSelector:@selector(updateClientComposingBuffer:) withObject:_currentDeferredClient afterDelay:0.0];
|
||||
// }
|
||||
// return;
|
||||
// }
|
||||
// TODO: handle this later
|
||||
|
||||
if ([_state isKindOfClass: [InputStateInputting class]] ) {
|
||||
NSString *buffer = [(InputStateInputting *)_state composingBuffer];
|
||||
buffer = [self _convertToSimplifiedChineseIfRequired:_composingBuffer];
|
||||
[client insertText:buffer replacementRange:NSMakeRange(NSNotFound, NSNotFound)];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handleState:(InputState *)newState client:(id)client
|
||||
{
|
||||
if ([newState isKindOfClass:[InputStateDeactive class]]) {
|
||||
// clean up reading buffer residues
|
||||
if (!_bpmfReadingBuffer->isEmpty()) {
|
||||
_bpmfReadingBuffer->clear();
|
||||
[client setMarkedText:@"" selectionRange:NSMakeRange(0, 0) replacementRange:NSMakeRange(NSNotFound, NSNotFound)];
|
||||
}
|
||||
|
||||
// commit any residue in the composing buffer
|
||||
[self commitCurrentTextIfRequired:client];
|
||||
|
||||
_currentDeferredClient = nil;
|
||||
_currentCandidateClient = nil;
|
||||
|
||||
gCurrentCandidateController.delegate = nil;
|
||||
gCurrentCandidateController.visible = NO;
|
||||
// [_candidates removeAllObjects];
|
||||
|
||||
[self _hideTooltip];
|
||||
|
||||
_state = newState;
|
||||
return;
|
||||
}
|
||||
|
||||
if ([newState isKindOfClass:[InputStateEmpty class]]) {
|
||||
[self commitCurrentTextIfRequired:client];
|
||||
|
||||
_builder->clear();
|
||||
_walkedNodes.clear();
|
||||
// [_composingBuffer setString:@""];
|
||||
gCurrentCandidateController.visible = NO;
|
||||
// [_candidates removeAllObjects];
|
||||
|
||||
[self _hideTooltip];
|
||||
|
||||
_state = newState;
|
||||
return;
|
||||
}
|
||||
|
||||
if ([newState isKindOfClass:[InputStateCommitting class]]) {
|
||||
NSString *poppedText = [(InputStateCommitting *)newState poppedText];
|
||||
[client insertText:poppedText replacementRange:NSMakeRange(NSNotFound, NSNotFound)];
|
||||
_state = newState;
|
||||
return;
|
||||
}
|
||||
|
||||
if ([newState isKindOfClass:[InputStateInputting class]]) {
|
||||
InputStateInputting *current = (InputStateInputting *)newState;
|
||||
NSString *poppedText = current.poppedText;
|
||||
if (poppedText.length) {
|
||||
[client insertText:poppedText replacementRange:NSMakeRange(NSNotFound, NSNotFound)];
|
||||
}
|
||||
|
||||
gCurrentCandidateController.visible = NO;
|
||||
[self _hideTooltip];
|
||||
|
||||
NSString *composedText = [(InputStateInputting *)newState composingBuffer];
|
||||
NSUInteger cursorIndex = [(InputStateInputting *)newState cursorIndex];
|
||||
// we must use NSAttributedString so that the cursor is visible --
|
||||
// can't just use NSString
|
||||
NSDictionary *attrDict = @{NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle),
|
||||
NSMarkedClauseSegmentAttributeName: @0};
|
||||
NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:composedText attributes:attrDict];
|
||||
|
||||
// the selection range is where the cursor is, with the length being 0 and replacement range NSNotFound,
|
||||
// i.e. the client app needs to take care of where to put ths composing buffer
|
||||
[client setMarkedText:attrString selectionRange:NSMakeRange(cursorIndex, 0) replacementRange:NSMakeRange(NSNotFound, NSNotFound)];
|
||||
// _latestReadingCursor = cursorIndex;
|
||||
|
||||
_state = newState;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Private methods
|
||||
|
||||
+ (VTHorizontalCandidateController *)horizontalCandidateController
|
||||
|
@ -1353,7 +1549,13 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
|
||||
NSRect lineHeightRect = NSMakeRect(0.0, 0.0, 16.0, 16.0);
|
||||
|
||||
NSInteger cursor = _latestReadingCursor;
|
||||
NSInteger cursor = 0;
|
||||
|
||||
// NSInteger cursor = _latestReadingCursor;
|
||||
if ([_state respondsToSelector:@selector(cursorIndex)]) {
|
||||
cursor = [[_state performSelector:@selector(cursorIndex)] integerValue];
|
||||
}
|
||||
|
||||
if (cursor == [_composingBuffer length] && cursor != 0) {
|
||||
cursor--;
|
||||
}
|
||||
|
@ -1477,7 +1679,12 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
|
|||
{
|
||||
NSRect lineHeightRect = NSMakeRect(0.0, 0.0, 16.0, 16.0);
|
||||
|
||||
NSInteger cursor = _latestReadingCursor;
|
||||
// NSInteger cursor = _latestReadingCursor;
|
||||
NSInteger cursor = 0;
|
||||
if ([_state respondsToSelector:@selector(cursorIndex)]) {
|
||||
cursor = [[_state performSelector:@selector(cursorIndex)] integerValue];
|
||||
}
|
||||
// zonble: should be handled with state.
|
||||
if (cursor == [_composingBuffer length] && cursor != 0) {
|
||||
cursor--;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
import Foundation
|
||||
|
||||
class InputState: NSObject {
|
||||
}
|
||||
|
||||
class InputStateDeactive: InputState {
|
||||
}
|
||||
|
||||
class InputStateEmpty: InputState {
|
||||
}
|
||||
|
||||
class InputStateCommitting: InputState {
|
||||
@objc private(set) var poppedText: String = ""
|
||||
|
||||
@objc convenience init(poppedText: String) {
|
||||
self.init()
|
||||
self.poppedText = poppedText
|
||||
}
|
||||
}
|
||||
|
||||
class InputStateInputting: InputState {
|
||||
@objc private(set) var composingBuffer: String = ""
|
||||
@objc private(set) var cursorIndex: UInt = 0
|
||||
@objc var poppedText: String = ""
|
||||
|
||||
@objc init(composingBuffer: String, cursorIndex: UInt) {
|
||||
self.composingBuffer = composingBuffer
|
||||
self.cursorIndex = cursorIndex
|
||||
}
|
||||
}
|
||||
|
||||
class InputStateMarking: InputStateInputting {
|
||||
@objc private(set) var markerIndex: UInt = 0
|
||||
@objc private(set) var markedRange: NSRange = NSRange(location: 0, length: 0)
|
||||
@objc var tooltip: String {
|
||||
return ""
|
||||
}
|
||||
|
||||
@objc init(composingBuffer: String, cursorIndex: UInt, markerIndex: UInt) {
|
||||
super.init(composingBuffer: composingBuffer, cursorIndex: cursorIndex)
|
||||
self.markerIndex = markerIndex
|
||||
let begin = min(cursorIndex, markerIndex)
|
||||
let end = max(cursorIndex, markerIndex)
|
||||
self.markedRange = NSMakeRange(Int(begin), Int( end - begin))
|
||||
}
|
||||
}
|
||||
|
||||
class InputStateChoosingCandidate: InputStateInputting {
|
||||
var markingRang: NSRange = NSRange(location: 0, length: 0)
|
||||
var candidates: [String] = []
|
||||
}
|
Loading…
Reference in New Issue