Implements adding phrase from shift and arrow keys.

This commit is contained in:
zonble 2022-01-09 19:17:20 +08:00 committed by Lukhnos Liu
parent 358462dff1
commit 6f761ecbcd
4 changed files with 177 additions and 87 deletions

View File

@ -37,6 +37,7 @@
#import "PreferencesWindowController.h" #import "PreferencesWindowController.h"
extern void LTLoadLanguageModel(void); extern void LTLoadLanguageModel(void);
extern void LTLoadUserLanguageModelFile(void);
static NSString *kCheckUpdateAutomatically = @"CheckUpdateAutomatically"; static NSString *kCheckUpdateAutomatically = @"CheckUpdateAutomatically";
static NSString *kNextUpdateCheckDateKey = @"NextUpdateCheckDate"; static NSString *kNextUpdateCheckDateKey = @"NextUpdateCheckDate";
@ -60,6 +61,7 @@ static const NSTimeInterval kTimeoutInterval = 60.0;
- (void)applicationDidFinishLaunching:(NSNotification *)inNotification - (void)applicationDidFinishLaunching:(NSNotification *)inNotification
{ {
LTLoadLanguageModel(); LTLoadLanguageModel();
LTLoadUserLanguageModelFile();
if (![[NSUserDefaults standardUserDefaults] objectForKey:kCheckUpdateAutomatically]) { if (![[NSUserDefaults standardUserDefaults] objectForKey:kCheckUpdateAutomatically]) {
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:kCheckUpdateAutomatically]; [[NSUserDefaults standardUserDefaults] setBool:YES forKey:kCheckUpdateAutomatically];

View File

@ -79,14 +79,14 @@ namespace Formosa {
inline BlockReadingBuilder::BlockReadingBuilder(LanguageModel *inLM) inline BlockReadingBuilder::BlockReadingBuilder(LanguageModel *inLM)
: m_LM(inLM) : m_LM(inLM)
, m_cursorIndex(0) , m_cursorIndex(0)
, m_markerCursorIndex(-1) , m_markerCursorIndex(SIZE_MAX)
{ {
} }
inline void BlockReadingBuilder::clear() inline void BlockReadingBuilder::clear()
{ {
m_cursorIndex = 0; m_cursorIndex = 0;
m_markerCursorIndex = -1; m_markerCursorIndex = SIZE_MAX;
m_readings.clear(); m_readings.clear();
m_grid.clear(); m_grid.clear();
} }
@ -113,6 +113,11 @@ namespace Formosa {
inline void BlockReadingBuilder::setMarkerCursorIndex(size_t inNewIndex) inline void BlockReadingBuilder::setMarkerCursorIndex(size_t inNewIndex)
{ {
if (inNewIndex == SIZE_MAX) {
m_markerCursorIndex = SIZE_MAX;
return;
}
m_markerCursorIndex = inNewIndex > m_readings.size() ? m_readings.size() : inNewIndex; m_markerCursorIndex = inNewIndex > m_readings.size() ? m_readings.size() : inNewIndex;
} }

View File

@ -47,6 +47,7 @@
// language model // language model
Formosa::Gramambular::FastLM *_languageModel; Formosa::Gramambular::FastLM *_languageModel;
Formosa::Gramambular::FastLM *_userPhrases;
// the grid (lattice) builder for the unigrams (and bigrams) // the grid (lattice) builder for the unigrams (and bigrams)
Formosa::Gramambular::BlockReadingBuilder* _builder; Formosa::Gramambular::BlockReadingBuilder* _builder;
@ -80,3 +81,4 @@
// the shared language model object // the shared language model object
extern "C" void LTLoadLanguageModel(); extern "C" void LTLoadLanguageModel();
extern "C" void LTLoadUserLanguageModelFile();

View File

@ -116,6 +116,21 @@ FastLM gLanguageModel;
FastLM gLanguageModelPlainBopomofo; FastLM gLanguageModelPlainBopomofo;
FastLM gUserPhraseLanguageModel; FastLM gUserPhraseLanguageModel;
static NSString *userDataFolderPath()
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDirectory, YES);
NSString *appSupportPath = [paths objectAtIndex:0];
NSString *userDictPath = [appSupportPath stringByAppendingPathComponent:@"McBopomofo"];
return userDictPath;
}
static NSString *userPhrasesDataPath()
{
return [userDataFolderPath() stringByAppendingPathComponent:@"data.txt"];
}
static const int kUserOverrideModelCapacity = 500; static const int kUserOverrideModelCapacity = 500;
static const double kObservedOverrideHalflife = 5400.0; // 1.5 hr. static const double kObservedOverrideHalflife = 5400.0; // 1.5 hr.
McBopomofo::UserOverrideModel gUserOverrideModel(kUserOverrideModelCapacity, kObservedOverrideHalflife); McBopomofo::UserOverrideModel gUserOverrideModel(kUserOverrideModelCapacity, kObservedOverrideHalflife);
@ -132,9 +147,7 @@ static inline NSString *LocalizationNotNeeded(NSString *s) {
+ (VTVerticalCandidateController *)verticalCandidateController; + (VTVerticalCandidateController *)verticalCandidateController;
- (void)collectCandidates; - (void)collectCandidates;
- (size_t)actualCandidateCursorIndex; - (size_t)actualCandidateCursorIndex;
- (void)_showCandidateWindowUsingVerticalMode:(BOOL)useVerticalMode client:(id)client; - (void)_showCandidateWindowUsingVerticalMode:(BOOL)useVerticalMode client:(id)client;
- (void)beep; - (void)beep;
@ -221,6 +234,9 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
chineseConversionMenuItem.state = _chineseConversionEnabled ? NSControlStateValueOn : NSControlStateValueOff; chineseConversionMenuItem.state = _chineseConversionEnabled ? NSControlStateValueOn : NSControlStateValueOff;
[menu addItem:chineseConversionMenuItem]; [menu addItem:chineseConversionMenuItem];
NSMenuItem *editUserPhraseItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Edit User Phrases", @"") action:@selector(openUserPhrases:) keyEquivalent:@""];
[menu addItem:editUserPhraseItem];
NSMenuItem *updateCheckItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Check for Updates…", @"") action:@selector(checkForUpdate:) keyEquivalent:@""]; NSMenuItem *updateCheckItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Check for Updates…", @"") action:@selector(checkForUpdate:) keyEquivalent:@""];
[menu addItem:updateCheckItem]; [menu addItem:updateCheckItem];
@ -393,6 +409,9 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
[_candidates removeAllObjects]; [_candidates removeAllObjects];
} }
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. // TODO: bug #28 is more likely to live in this method.
- (void)updateClientComposingBuffer:(id)client - (void)updateClientComposingBuffer:(id)client
{ {
@ -446,33 +465,39 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
NSString *composedText = [head stringByAppendingString:[reading stringByAppendingString:tail]]; NSString *composedText = [head stringByAppendingString:[reading stringByAppendingString:tail]];
NSInteger cursorIndex = composedStringCursorIndex + [reading length]; NSInteger cursorIndex = composedStringCursorIndex + [reading length];
// we must use NSAttributedString so that the cursor is visible -- if (_bpmfReadingBuffer->isEmpty() && _builder->markerCursorIndex() != SIZE_MAX) {
// can't just use NSString NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:composedText];
NSDictionary *attrDict = @{NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle), size_t begin = min(_builder->markerCursorIndex(), _builder->cursorIndex());
NSMarkedClauseSegmentAttributeName: @0}; size_t end = max(_builder->markerCursorIndex(), _builder->cursorIndex());
NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:composedText attributes:attrDict]; [attrString setAttributes:@{
NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle),
NSMarkedClauseSegmentAttributeName: @0
} range:NSMakeRange(0, begin)];
[attrString setAttributes:@{
NSUnderlineStyleAttributeName: @(NSUnderlineStyleThick),
NSMarkedClauseSegmentAttributeName: @1
} range:NSMakeRange(begin, end - begin)];
[attrString setAttributes:@{
NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle),
NSMarkedClauseSegmentAttributeName: @2
} range:NSMakeRange(end, [composedText length] - end)];
NSLog(@"marked %@", [self _currentMarkedText]);
NSLog(@"attrString %@", attrString);
[client setMarkedText:attrString selectionRange:NSMakeRange((NSInteger)_builder->markerCursorIndex(), 0) replacementRange:NSMakeRange(NSNotFound, NSNotFound)];
_latestReadingCursor = (NSInteger)_builder->markerCursorIndex();
if (_bpmfReadingBuffer->isEmpty() && _builder->markerCursorIndex() != -1) { } else {
NSInteger begin = 0; // we must use NSAttributedString so that the cursor is visible --
NSInteger end = 0; // can't just use NSString
if (_builder->markerCursorIndex() > _builder->cursorIndex()) { NSDictionary *attrDict = @{NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle),
begin = _builder->cursorIndex();
end = _builder->markerCursorIndex();
} else {
end = _builder->cursorIndex();
begin = _builder->markerCursorIndex();
}
NSRange range = NSMakeRange(begin, end - begin);
NSDictionary *highlightAttrDict = @{NSUnderlineStyleAttributeName: @(NSUnderlineStyleDouble),
NSMarkedClauseSegmentAttributeName: @0}; NSMarkedClauseSegmentAttributeName: @0};
[attrString addAttributes:highlightAttrDict range:range]; 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;
} }
// 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;
} }
- (void)walk - (void)walk
@ -576,25 +601,22 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
return layout; return layout;
} }
- (NSString *)_currentHighlighText - (NSString *)_currentMarkedText
{ {
if (_builder->markerCursorIndex() == -1) { if (_builder->markerCursorIndex() < 0) {
return @""; return @"";
} }
if (!_bpmfReadingBuffer->isEmpty()) { if (!_bpmfReadingBuffer->isEmpty()) {
return @""; return @"";
} }
NSInteger begin = 0; size_t begin = min(_builder->markerCursorIndex(), _builder->cursorIndex());
NSInteger end = 0; size_t end = max(_builder->markerCursorIndex(), _builder->cursorIndex());
if (_builder->markerCursorIndex() > _builder->cursorIndex()) { if (end - begin < 2) {
begin = _builder->cursorIndex(); return @"";
end = _builder->markerCursorIndex();
} else {
end = _builder->cursorIndex();
begin = _builder->markerCursorIndex();
} }
NSRange range = NSMakeRange(begin, end - begin);
NSRange range = NSMakeRange((NSInteger)begin, (NSInteger)(end - begin));
NSString *reading = [_composingBuffer substringWithRange:range]; NSString *reading = [_composingBuffer substringWithRange:range];
NSMutableString *string = [[NSMutableString alloc] init]; NSMutableString *string = [[NSMutableString alloc] init];
[string appendString:reading]; [string appendString:reading];
@ -611,6 +633,39 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
return string; return string;
} }
- (BOOL)_writeUserPhrase
{
NSString *currentMarkedPhrase = [self _currentMarkedText];
if (![currentMarkedPhrase length]) {
return NO;
}
NSString *path = userPhrasesDataPath();
if (![[NSFileManager defaultManager] fileExistsAtPath:path]) {
BOOL result = [[@"" dataUsingEncoding:NSUTF8StringEncoding] writeToFile:path atomically:YES];
if (result) {
NSLog(@"user phrases file is creates");
}
else {
NSLog(@"failed to create user phrases file");
return NO;
}
}
NSLog(@"About to write %@ into %@", currentMarkedPhrase, path);
currentMarkedPhrase = [currentMarkedPhrase stringByAppendingString:@"\n"];
NSFileHandle *file = [NSFileHandle fileHandleForUpdatingAtPath:path];
NSLog(@"file %@", file);
if (!file) {
NSLog(@"Failed to write to %@", path);
return NO;
}
[file seekToEndOfFile];
NSData *data = [currentMarkedPhrase dataUsingEncoding:NSUTF8StringEncoding];
[file writeData:data];
[file closeFile];
return YES;
}
- (BOOL)handleInputText:(NSString*)inputText key:(NSInteger)keyCode modifiers:(NSUInteger)flags client:(id)client - (BOOL)handleInputText:(NSString*)inputText key:(NSInteger)keyCode modifiers:(NSUInteger)flags client:(id)client
{ {
NSRect textFrame = NSZeroRect; NSRect textFrame = NSZeroRect;
@ -651,7 +706,6 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
return NO; return NO;
} }
// Caps Lock processing : if Caps Lock is on, temporarily disable bopomofo. // Caps Lock processing : if Caps Lock is on, temporarily disable bopomofo.
if (charCode == 8 || charCode == 13 || keyCode == absorbedArrowKey || keyCode == extraChooseCandidateKey || keyCode == cursorForwardKey || keyCode == cursorBackwardKey) { if (charCode == 8 || charCode == 13 || keyCode == absorbedArrowKey || keyCode == extraChooseCandidateKey || keyCode == cursorForwardKey || keyCode == cursorBackwardKey) {
// do nothing if backspace is pressed -- we ignore the key // do nothing if backspace is pressed -- we ignore the key
@ -695,6 +749,50 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
return [self handleCandidateEventWithInputText:inputText charCode:charCode keyCode:keyCode]; return [self handleCandidateEventWithInputText:inputText charCode:charCode keyCode:keyCode];
} }
// If we have marker index.
if (_builder->markerCursorIndex() != SIZE_MAX) {
// ESC
if (charCode == 27) {
_builder->setMarkerCursorIndex(SIZE_MAX);
[self updateClientComposingBuffer:client];
return YES;
}
// Enter
if (charCode == 13) {
if ([self _writeUserPhrase]) {
_builder->setMarkerCursorIndex(SIZE_MAX);
} else {
[self beep];
}
[self updateClientComposingBuffer:client];
return YES;
}
// Shift + left
if (keyCode == cursorBackwardKey && (flags & NSShiftKeyMask)) {
if (_builder->markerCursorIndex() > 0) {
_builder->setMarkerCursorIndex(_builder->markerCursorIndex() - 1);
}
else {
[self beep];
}
[self updateClientComposingBuffer:client];
return YES;
}
// Shift + Right
if (keyCode == cursorForwardKey && (flags & NSShiftKeyMask)) {
if (_builder->markerCursorIndex() < _builder->length()) {
_builder->setMarkerCursorIndex(_builder->markerCursorIndex() + 1);
}
else {
[self beep];
}
[self updateClientComposingBuffer:client];
return YES;
}
_builder->setMarkerCursorIndex(SIZE_MAX);
}
// see if it's valid BPMF reading // see if it's valid BPMF reading
if (_bpmfReadingBuffer->isValidKey((char)charCode)) { if (_bpmfReadingBuffer->isValidKey((char)charCode)) {
_bpmfReadingBuffer->combineKey((char)charCode); _bpmfReadingBuffer->combineKey((char)charCode);
@ -824,21 +922,11 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
} }
if (flags & NSShiftKeyMask) { if (flags & NSShiftKeyMask) {
if (_builder->markerCursorIndex() == -1) { if (_builder->cursorIndex() > 0) {
if (_builder->cursorIndex() > 0) { _builder->setMarkerCursorIndex(_builder->cursorIndex() - 1);
_builder->setMarkerCursorIndex(_builder->cursorIndex() - 1);
} else {
[self beep];
}
NSString *tmp = [self _currentHighlighText];
NSLog(@"%@", tmp);
} }
else { else {
if (_builder->markerCursorIndex() > 0) { [self beep];
_builder->setMarkerCursorIndex(_builder->markerCursorIndex() - 1);
} else {
[self beep];
}
} }
} else { } else {
if (_builder->cursorIndex() > 0) { if (_builder->cursorIndex() > 0) {
@ -865,23 +953,10 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
} }
if (flags & NSShiftKeyMask) { if (flags & NSShiftKeyMask) {
if (_builder->markerCursorIndex() == -1) { if (_builder->cursorIndex() < _builder->length()) {
if (_builder->cursorIndex() < _builder->length()) { _builder->setMarkerCursorIndex(_builder->cursorIndex() + 1);
_builder->setMarkerCursorIndex(_builder->cursorIndex() + 1); } else {
} else { [self beep];
[self beep];
}
NSString *tmp = [self _currentHighlighText];
NSLog(@"%@", tmp);
}
else {
if (_builder->markerCursorIndex() < _builder->length()) {
_builder->setMarkerCursorIndex(_builder->markerCursorIndex() + 1);
} else {
[self beep];
}
NSString *tmp = [self _currentHighlighText];
NSLog(@"%@", tmp);
} }
} else { } else {
if (_builder->cursorIndex() < _builder->length()) { if (_builder->cursorIndex() < _builder->length()) {
@ -1463,6 +1538,22 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
[(AppDelegate *)[[NSApplication sharedApplication] delegate] checkForUpdateForced:YES]; [(AppDelegate *)[[NSApplication sharedApplication] delegate] checkForUpdateForced:YES];
} }
- (void)openUserPhrases:(id)sender
{
NSString *path = userPhrasesDataPath();
NSLog(@"path: %@", path);
if (![[NSFileManager defaultManager] fileExistsAtPath:path]) {
[[@"" dataUsingEncoding:NSUTF8StringEncoding] writeToFile:path atomically:YES];
}
NSURL *url = [NSURL fileURLWithPath:path];
[[NSWorkspace sharedWorkspace] openURL:url];
// NSWorkspaceOpenConfiguration *config = [NSWorkspaceOpenConfiguration configuration];
// [[NSWorkspace sharedWorkspace] openURL:url configuration:config completionHandler:^(NSRunningApplication * app, NSError * error) {
// NSLog(@"app %@", app);
// NSLog(@"error %@", error);
// }];
}
- (void)showAbout:(id)sender - (void)showAbout:(id)sender
{ {
[[NSApplication sharedApplication] orderFrontStandardAboutPanel:sender]; [[NSApplication sharedApplication] orderFrontStandardAboutPanel:sender];
@ -1511,12 +1602,6 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
@end @end
static NSString *userDataFolderPath() {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDirectory, YES);
NSString *appSupportPath = [paths objectAtIndex:0];
NSString *userDictPath = [appSupportPath stringByAppendingPathComponent:@"McBopomofo"];
return userDictPath;
}
static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, FastLM &lm) static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, FastLM &lm)
{ {
@ -1527,18 +1612,14 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, FastLM &
} }
} }
static void LTLoadUserLanguageModelFile(NSString *filenameWithoutExtension, FastLM &lm)
{
NSString *filename = [filenameWithoutExtension stringByAppendingPathExtension:@"txt"];
NSString *dataPath = [userDataFolderPath() stringByAppendingPathComponent:filename];
bool result = lm.open([dataPath UTF8String]);
if (!result) {
NSLog(@"Failed opening language model: %@", dataPath);
}
}
void LTLoadLanguageModel() void LTLoadLanguageModel()
{ {
LTLoadLanguageModelFile(@"data", gLanguageModel); LTLoadLanguageModelFile(@"data", gLanguageModel);
LTLoadLanguageModelFile(@"data-plain-bpmf", gLanguageModelPlainBopomofo); LTLoadLanguageModelFile(@"data-plain-bpmf", gLanguageModelPlainBopomofo);
} }
void LTLoadUserLanguageModelFile()
{
gUserPhraseLanguageModel.close();
gUserPhraseLanguageModel.open([userPhrasesDataPath() UTF8String]);
}