Zonble: UserPhrases // Need further modifications for CHS mode.
- Raw sync from McBopomofo repository with insufficient compatibility modifications.
This commit is contained in:
parent
0c80acbf98
commit
2cbecd3462
|
@ -42,6 +42,7 @@
|
||||||
#import "frmAboutWindow.h"
|
#import "frmAboutWindow.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";
|
||||||
|
@ -66,6 +67,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];
|
||||||
|
|
|
@ -38,7 +38,7 @@ namespace Formosa {
|
||||||
|
|
||||||
class BlockReadingBuilder {
|
class BlockReadingBuilder {
|
||||||
public:
|
public:
|
||||||
BlockReadingBuilder(LanguageModel *inLM);
|
BlockReadingBuilder(LanguageModel *inLM, LanguageModel *inUserPhraseLM);
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
size_t length() const;
|
size_t length() const;
|
||||||
|
@ -53,6 +53,10 @@ namespace Formosa {
|
||||||
void setJoinSeparator(const string& separator);
|
void setJoinSeparator(const string& separator);
|
||||||
const string joinSeparator() const;
|
const string joinSeparator() const;
|
||||||
|
|
||||||
|
size_t markerCursorIndex() const;
|
||||||
|
void setMarkerCursorIndex(size_t inNewIndex);
|
||||||
|
vector<string> readingsAtRange(size_t begin, size_t end) const;
|
||||||
|
|
||||||
Grid& grid();
|
Grid& grid();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -60,26 +64,31 @@ namespace Formosa {
|
||||||
|
|
||||||
static const string Join(vector<string>::const_iterator begin, vector<string>::const_iterator end, const string& separator);
|
static const string Join(vector<string>::const_iterator begin, vector<string>::const_iterator end, const string& separator);
|
||||||
|
|
||||||
//最多使用六個字組成一個詞
|
//最多使用六個字組成一個詞
|
||||||
static const size_t MaximumBuildSpanLength = 6;
|
static const size_t MaximumBuildSpanLength = 6;
|
||||||
|
|
||||||
size_t m_cursorIndex;
|
size_t m_cursorIndex;
|
||||||
|
size_t m_markerCursorIndex;
|
||||||
vector<string> m_readings;
|
vector<string> m_readings;
|
||||||
|
|
||||||
Grid m_grid;
|
Grid m_grid;
|
||||||
LanguageModel *m_LM;
|
LanguageModel *m_LM;
|
||||||
|
LanguageModel *m_UserPhraseLM;
|
||||||
string m_joinSeparator;
|
string m_joinSeparator;
|
||||||
};
|
};
|
||||||
|
|
||||||
inline BlockReadingBuilder::BlockReadingBuilder(LanguageModel *inLM)
|
inline BlockReadingBuilder::BlockReadingBuilder(LanguageModel *inLM, LanguageModel *inUserPhraseLM)
|
||||||
: m_LM(inLM)
|
: m_LM(inLM)
|
||||||
|
, m_UserPhraseLM(inUserPhraseLM)
|
||||||
, m_cursorIndex(0)
|
, m_cursorIndex(0)
|
||||||
|
, m_markerCursorIndex(SIZE_MAX)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void BlockReadingBuilder::clear()
|
inline void BlockReadingBuilder::clear()
|
||||||
{
|
{
|
||||||
m_cursorIndex = 0;
|
m_cursorIndex = 0;
|
||||||
|
m_markerCursorIndex = SIZE_MAX;
|
||||||
m_readings.clear();
|
m_readings.clear();
|
||||||
m_grid.clear();
|
m_grid.clear();
|
||||||
}
|
}
|
||||||
|
@ -99,6 +108,20 @@ namespace Formosa {
|
||||||
m_cursorIndex = inNewIndex > m_readings.size() ? m_readings.size() : inNewIndex;
|
m_cursorIndex = inNewIndex > m_readings.size() ? m_readings.size() : inNewIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline size_t BlockReadingBuilder::markerCursorIndex() const
|
||||||
|
{
|
||||||
|
return m_markerCursorIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
inline void BlockReadingBuilder::insertReadingAtCursor(const string& inReading)
|
inline void BlockReadingBuilder::insertReadingAtCursor(const string& inReading)
|
||||||
{
|
{
|
||||||
|
@ -109,6 +132,14 @@ namespace Formosa {
|
||||||
m_cursorIndex++;
|
m_cursorIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline vector<string> BlockReadingBuilder::readingsAtRange(size_t begin, size_t end) const {
|
||||||
|
vector<string> v;
|
||||||
|
for (size_t i = begin; i < end; i++) {
|
||||||
|
v.push_back(m_readings[i]);
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
inline bool BlockReadingBuilder::deleteReadingBeforeCursor()
|
inline bool BlockReadingBuilder::deleteReadingBeforeCursor()
|
||||||
{
|
{
|
||||||
if (!m_cursorIndex) {
|
if (!m_cursorIndex) {
|
||||||
|
@ -190,10 +221,25 @@ namespace Formosa {
|
||||||
for (size_t p = begin ; p < end ; p++) {
|
for (size_t p = begin ; p < end ; p++) {
|
||||||
for (size_t q = 1 ; q <= MaximumBuildSpanLength && p+q <= end ; q++) {
|
for (size_t q = 1 ; q <= MaximumBuildSpanLength && p+q <= end ; q++) {
|
||||||
string combinedReading = Join(m_readings.begin() + p, m_readings.begin() + p + q, m_joinSeparator);
|
string combinedReading = Join(m_readings.begin() + p, m_readings.begin() + p + q, m_joinSeparator);
|
||||||
|
if (!m_grid.hasNodeAtLocationSpanningLengthMatchingKey(p, q, combinedReading)) {
|
||||||
|
vector<Unigram> unigrams;
|
||||||
|
|
||||||
if (m_LM->hasUnigramsForKey(combinedReading) && !m_grid.hasNodeAtLocationSpanningLengthMatchingKey(p, q, combinedReading)) {
|
if (m_UserPhraseLM != NULL) {
|
||||||
Node n(combinedReading, m_LM->unigramsForKeys(combinedReading), vector<Bigram>());
|
if (m_UserPhraseLM->hasUnigramsForKey(combinedReading)) {
|
||||||
m_grid.insertNode(n, p, q);
|
vector<Unigram> userUnigrams = m_UserPhraseLM->unigramsForKeys(combinedReading);
|
||||||
|
unigrams.insert(unigrams.end(), userUnigrams.begin(), userUnigrams.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_LM->hasUnigramsForKey(combinedReading)) {
|
||||||
|
vector<Unigram> globalUnigrams = m_LM->unigramsForKeys(combinedReading);
|
||||||
|
unigrams.insert(unigrams.end(), globalUnigrams.begin(), globalUnigrams.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unigrams.size() > 0) {
|
||||||
|
Node n(combinedReading, unigrams, vector<Bigram>());
|
||||||
|
m_grid.insertNode(n, p, q);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,6 +52,7 @@
|
||||||
|
|
||||||
// language model
|
// language model
|
||||||
Formosa::Gramambular::FastLM *_languageModel;
|
Formosa::Gramambular::FastLM *_languageModel;
|
||||||
|
Formosa::Gramambular::FastLM *_userPhrasesModel;
|
||||||
|
|
||||||
// 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;
|
||||||
|
@ -88,3 +89,4 @@
|
||||||
|
|
||||||
// the shared language model object
|
// the shared language model object
|
||||||
extern "C" void LTLoadLanguageModel();
|
extern "C" void LTLoadLanguageModel();
|
||||||
|
extern "C" void LTLoadUserLanguageModelFile();
|
||||||
|
|
|
@ -45,6 +45,7 @@
|
||||||
#import "AppDelegate.h"
|
#import "AppDelegate.h"
|
||||||
#import "VTHorizontalCandidateController.h"
|
#import "VTHorizontalCandidateController.h"
|
||||||
#import "VTVerticalCandidateController.h"
|
#import "VTVerticalCandidateController.h"
|
||||||
|
#import "OVNonModalAlertWindowController.h"
|
||||||
#import "vChewing-Swift.h"
|
#import "vChewing-Swift.h"
|
||||||
|
|
||||||
|
|
||||||
|
@ -119,12 +120,69 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot";
|
||||||
// shared language model object that stores our phrase-term probability database
|
// shared language model object that stores our phrase-term probability database
|
||||||
FastLM gLanguageModelCHT;
|
FastLM gLanguageModelCHT;
|
||||||
FastLM gLanguageModelCHS;
|
FastLM gLanguageModelCHS;
|
||||||
|
FastLM gUserPhraseLanguageModelCHT;
|
||||||
|
FastLM gUserPhraseLanguageModelCHS;
|
||||||
|
|
||||||
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.
|
||||||
vChewing::UserOverrideModel gUserOverrideModelCHT(kUserOverrideModelCapacity, kObservedOverrideHalflife);
|
vChewing::UserOverrideModel gUserOverrideModelCHT(kUserOverrideModelCapacity, kObservedOverrideHalflife);
|
||||||
vChewing::UserOverrideModel gUserOverrideModelCHS(kUserOverrideModelCapacity, kObservedOverrideHalflife);
|
vChewing::UserOverrideModel gUserOverrideModelCHS(kUserOverrideModelCapacity, kObservedOverrideHalflife);
|
||||||
|
|
||||||
|
static NSString *LTUserDataFolderPath()
|
||||||
|
{
|
||||||
|
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDirectory, YES);
|
||||||
|
NSString *appSupportPath = [paths objectAtIndex:0];
|
||||||
|
NSString *userDictPath = [appSupportPath stringByAppendingPathComponent:@"vChewing"];
|
||||||
|
return userDictPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
static NSString *LTUserPhrasesDataPathCHT()
|
||||||
|
{
|
||||||
|
return [LTUserDataFolderPath() stringByAppendingPathComponent:@"userdata-cht.txt"];
|
||||||
|
}
|
||||||
|
|
||||||
|
// static NSString *LTUserPhrasesDataPathCHS()
|
||||||
|
// {
|
||||||
|
// return [LTUserDataFolderPath() stringByAppendingPathComponent:@"userdata-chs.txt"];
|
||||||
|
// }
|
||||||
|
|
||||||
|
static BOOL LTCheckIfUserLanguageModelFileExists() {
|
||||||
|
|
||||||
|
NSString *folderPath = LTUserDataFolderPath();
|
||||||
|
BOOL isFolder = NO;
|
||||||
|
BOOL folderExist = [[NSFileManager defaultManager] fileExistsAtPath:folderPath isDirectory:&isFolder];
|
||||||
|
if (folderExist && !isFolder) {
|
||||||
|
NSError *error = nil;
|
||||||
|
[[NSFileManager defaultManager] removeItemAtPath:folderPath error:&error];
|
||||||
|
if (error) {
|
||||||
|
NSLog(@"Failed to remove folder %@", error);
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
folderExist = NO;
|
||||||
|
}
|
||||||
|
if (!folderExist) {
|
||||||
|
NSError *error = nil;
|
||||||
|
[[NSFileManager defaultManager] createDirectoryAtPath:folderPath withIntermediateDirectories:YES attributes:nil error:&error];
|
||||||
|
if (error) {
|
||||||
|
NSLog(@"Failed to create folder %@", error);
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString *filePath = LTUserPhrasesDataPathCHT();
|
||||||
|
if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
|
||||||
|
BOOL result = [[@"" dataUsingEncoding:NSUTF8StringEncoding] writeToFile:filePath atomically:YES];
|
||||||
|
if (!result) {
|
||||||
|
NSLog(@"Failed to write file");
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// https://clang-analyzer.llvm.org/faq.html
|
// https://clang-analyzer.llvm.org/faq.html
|
||||||
__attribute__((annotate("returns_localized_nsstring")))
|
__attribute__((annotate("returns_localized_nsstring")))
|
||||||
static inline NSString *LocalizationNotNeeded(NSString *s) {
|
static inline NSString *LocalizationNotNeeded(NSString *s) {
|
||||||
|
@ -200,7 +258,8 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
|
||||||
|
|
||||||
// create the lattice builder
|
// create the lattice builder
|
||||||
_languageModel = &gLanguageModelCHT;
|
_languageModel = &gLanguageModelCHT;
|
||||||
_builder = new BlockReadingBuilder(_languageModel);
|
_userPhrasesModel = &gUserPhraseLanguageModelCHT;
|
||||||
|
_builder = new BlockReadingBuilder(_languageModel, _userPhrasesModel);
|
||||||
_uom = &gUserOverrideModelCHT;
|
_uom = &gUserOverrideModelCHT;
|
||||||
|
|
||||||
// each Mandarin syllable is separated by a hyphen
|
// each Mandarin syllable is separated by a hyphen
|
||||||
|
@ -232,12 +291,29 @@ 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];
|
||||||
|
|
||||||
|
// Needs improvement for Simplified Chinese Input
|
||||||
|
if (_inputMode != kSimpBopomofoModeIdentifier) {
|
||||||
|
[menu addItem:[NSMenuItem separatorItem]];
|
||||||
|
[menu addItemWithTitle:NSLocalizedString(@"User Phrases", @"") action:NULL keyEquivalent:@""];
|
||||||
|
NSMenuItem *editUserPheaseItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Edit User Phrases", @"") action:@selector(openUserPhrases:) keyEquivalent:@""];
|
||||||
|
[editUserPheaseItem setIndentationLevel:2];
|
||||||
|
[menu addItem:editUserPheaseItem];
|
||||||
|
|
||||||
|
NSMenuItem *reloadUserPheaseItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Reload User Phrases", @"") action:@selector(reloadUserPhrases:) keyEquivalent:@""];
|
||||||
|
[reloadUserPheaseItem setIndentationLevel:2];
|
||||||
|
[menu addItem:reloadUserPheaseItem];
|
||||||
|
[menu addItem:[NSMenuItem separatorItem]];
|
||||||
|
}
|
||||||
|
|
||||||
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];
|
||||||
|
|
||||||
NSMenuItem *aboutMenuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"About vChewing…", @"") action:@selector(showAbout:) keyEquivalent:@""];
|
NSMenuItem *aboutMenuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"About vChewing…", @"") action:@selector(showAbout:) keyEquivalent:@""];
|
||||||
[menu addItem:aboutMenuItem];
|
[menu addItem:aboutMenuItem];
|
||||||
|
|
||||||
|
// Menu Debug Purposes...
|
||||||
|
NSLog(@"menu %@", menu);
|
||||||
|
|
||||||
return menu;
|
return menu;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -333,24 +409,24 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
|
||||||
{
|
{
|
||||||
NSString *newInputMode;
|
NSString *newInputMode;
|
||||||
Formosa::Gramambular::FastLM *newLanguageModel;
|
Formosa::Gramambular::FastLM *newLanguageModel;
|
||||||
vChewing::UserOverrideModel *newUom;
|
Formosa::Gramambular::FastLM *userPhraseModel;
|
||||||
|
|
||||||
if ([value isKindOfClass:[NSString class]] && [value isEqual:kSimpBopomofoModeIdentifier]) {
|
if ([value isKindOfClass:[NSString class]] && [value isEqual:kSimpBopomofoModeIdentifier]) {
|
||||||
newInputMode = kSimpBopomofoModeIdentifier;
|
newInputMode = kSimpBopomofoModeIdentifier;
|
||||||
newLanguageModel = &gLanguageModelCHS;
|
newLanguageModel = &gLanguageModelCHS;
|
||||||
newUom = &gUserOverrideModelCHS;
|
userPhraseModel = &gUserPhraseLanguageModelCHS;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
newInputMode = kBopomofoModeIdentifier;
|
newInputMode = kBopomofoModeIdentifier;
|
||||||
newLanguageModel = &gLanguageModelCHT;
|
newLanguageModel = &gLanguageModelCHT;
|
||||||
newUom = &gUserOverrideModelCHT;
|
userPhraseModel = &gUserPhraseLanguageModelCHT;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only apply the changes if the value is changed
|
// Only apply the changes if the value is changed
|
||||||
if (![_inputMode isEqualToString:newInputMode]) {
|
if (![_inputMode isEqualToString:newInputMode]) {
|
||||||
[[NSUserDefaults standardUserDefaults] synchronize];
|
[[NSUserDefaults standardUserDefaults] synchronize];
|
||||||
|
|
||||||
// Remember to override the keyboard layout again -- treat this as an activate eventy
|
// Remember to override the keyboard layout again -- treat this as an activate event
|
||||||
NSString *basisKeyboardLayoutID = [[NSUserDefaults standardUserDefaults] stringForKey:kBasisKeyboardLayoutPreferenceKey];
|
NSString *basisKeyboardLayoutID = [[NSUserDefaults standardUserDefaults] stringForKey:kBasisKeyboardLayoutPreferenceKey];
|
||||||
if (!basisKeyboardLayoutID) {
|
if (!basisKeyboardLayoutID) {
|
||||||
basisKeyboardLayoutID = @"com.apple.keylayout.US";
|
basisKeyboardLayoutID = @"com.apple.keylayout.US";
|
||||||
|
@ -359,6 +435,7 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
|
||||||
|
|
||||||
_inputMode = newInputMode;
|
_inputMode = newInputMode;
|
||||||
_languageModel = newLanguageModel;
|
_languageModel = newLanguageModel;
|
||||||
|
_userPhrasesModel = userPhraseModel;
|
||||||
|
|
||||||
if (!_bpmfReadingBuffer->isEmpty()) {
|
if (!_bpmfReadingBuffer->isEmpty()) {
|
||||||
_bpmfReadingBuffer->clear();
|
_bpmfReadingBuffer->clear();
|
||||||
|
@ -371,10 +448,9 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
|
||||||
|
|
||||||
if (_builder) {
|
if (_builder) {
|
||||||
delete _builder;
|
delete _builder;
|
||||||
_builder = new BlockReadingBuilder(_languageModel);
|
_builder = new BlockReadingBuilder(_languageModel, _userPhrasesModel);
|
||||||
_builder->setJoinSeparator("-");
|
_builder->setJoinSeparator("-");
|
||||||
}
|
}
|
||||||
_uom = newUom;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -408,6 +484,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
|
||||||
{
|
{
|
||||||
|
@ -461,17 +540,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
|
// if there is a marked range, we need to tear the string into three parts.
|
||||||
NSDictionary *attrDict = @{NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle),
|
NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:composedText];
|
||||||
NSMarkedClauseSegmentAttributeName: @0};
|
size_t begin = min(_builder->markerCursorIndex(), _builder->cursorIndex());
|
||||||
NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:composedText attributes:attrDict];
|
size_t end = max(_builder->markerCursorIndex(), _builder->cursorIndex());
|
||||||
|
[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)];
|
||||||
|
// 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((NSInteger)_builder->markerCursorIndex(), 0) replacementRange:NSMakeRange(NSNotFound, NSNotFound)];
|
||||||
|
_latestReadingCursor = (NSInteger)_builder->markerCursorIndex();
|
||||||
|
} else {
|
||||||
|
// 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,
|
// 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
|
// 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)];
|
[client setMarkedText:attrString selectionRange:NSMakeRange(cursorIndex, 0) replacementRange:NSMakeRange(NSNotFound, NSNotFound)];
|
||||||
|
_latestReadingCursor = cursorIndex;
|
||||||
_latestReadingCursor = cursorIndex;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)walk
|
- (void)walk
|
||||||
|
@ -575,6 +676,64 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
|
||||||
return layout;
|
return layout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (NSString *)_currentMarkedText
|
||||||
|
{
|
||||||
|
if (_builder->markerCursorIndex() < 0) {
|
||||||
|
return @"";
|
||||||
|
}
|
||||||
|
if (!_bpmfReadingBuffer->isEmpty()) {
|
||||||
|
return @"";
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t begin = min(_builder->markerCursorIndex(), _builder->cursorIndex());
|
||||||
|
size_t end = max(_builder->markerCursorIndex(), _builder->cursorIndex());
|
||||||
|
// A phrase should contian at least two characters.
|
||||||
|
if (end - begin < 2) {
|
||||||
|
return @"";
|
||||||
|
}
|
||||||
|
|
||||||
|
NSRange range = NSMakeRange((NSInteger)begin, (NSInteger)(end - begin));
|
||||||
|
NSString *reading = [_composingBuffer substringWithRange:range];
|
||||||
|
NSMutableString *string = [[NSMutableString alloc] init];
|
||||||
|
[string appendString:reading];
|
||||||
|
[string appendString:@" "];
|
||||||
|
NSMutableArray *readingsArray = [[NSMutableArray alloc] init];
|
||||||
|
vector<std::string> v = _builder->readingsAtRange(begin,end);
|
||||||
|
for(vector<std::string>::iterator it_i=v.begin(); it_i!=v.end(); ++it_i) {
|
||||||
|
[readingsArray addObject:[NSString stringWithUTF8String:it_i->c_str()]];
|
||||||
|
}
|
||||||
|
[string appendString:[readingsArray componentsJoinedByString:@"-"]];
|
||||||
|
[string appendString:@" "];
|
||||||
|
[string appendString:@"-1.0"];
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)_writeUserPhrase
|
||||||
|
{
|
||||||
|
if (!LTCheckIfUserLanguageModelFileExists()) {
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString *currentMarkedPhrase = [self _currentMarkedText];
|
||||||
|
if (![currentMarkedPhrase length]) {
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentMarkedPhrase = [currentMarkedPhrase stringByAppendingString:@"\n"];
|
||||||
|
NSString *path = LTUserPhrasesDataPathCHT();
|
||||||
|
NSFileHandle *file = [NSFileHandle fileHandleForUpdatingAtPath:path];
|
||||||
|
if (!file) {
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
[file seekToEndOfFile];
|
||||||
|
NSData *data = [currentMarkedPhrase dataUsingEncoding:NSUTF8StringEncoding];
|
||||||
|
[file writeData:data];
|
||||||
|
[file closeFile];
|
||||||
|
|
||||||
|
LTLoadUserLanguageModelFile();
|
||||||
|
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;
|
||||||
|
@ -615,7 +774,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
|
||||||
|
@ -659,6 +817,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);
|
||||||
|
@ -781,11 +983,21 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_builder->cursorIndex() > 0) {
|
if (flags & NSShiftKeyMask) {
|
||||||
_builder->setCursorIndex(_builder->cursorIndex() - 1);
|
// Shift + left
|
||||||
}
|
if (_builder->cursorIndex() > 0) {
|
||||||
else {
|
_builder->setMarkerCursorIndex(_builder->cursorIndex() - 1);
|
||||||
[self beep];
|
}
|
||||||
|
else {
|
||||||
|
[self beep];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (_builder->cursorIndex() > 0) {
|
||||||
|
_builder->setCursorIndex(_builder->cursorIndex() - 1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
[self beep];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -803,11 +1015,20 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_builder->cursorIndex() < _builder->length()) {
|
if (flags & NSShiftKeyMask) {
|
||||||
_builder->setCursorIndex(_builder->cursorIndex() + 1);
|
// Shift + Right
|
||||||
}
|
if (_builder->cursorIndex() < _builder->length()) {
|
||||||
else {
|
_builder->setMarkerCursorIndex(_builder->cursorIndex() + 1);
|
||||||
[self beep];
|
} else {
|
||||||
|
[self beep];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (_builder->cursorIndex() < _builder->length()) {
|
||||||
|
_builder->setCursorIndex(_builder->cursorIndex() + 1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
[self beep];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1333,6 +1554,30 @@ 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
|
||||||
|
{
|
||||||
|
NSLog(@"openUserPhrases called");
|
||||||
|
if (!LTCheckIfUserLanguageModelFileExists()) {
|
||||||
|
NSString *content = [NSString stringWithFormat:NSLocalizedString(@"Please check the permission of at \"%@\".", @""), LTUserDataFolderPath()];
|
||||||
|
[[OVNonModalAlertWindowController sharedInstance] showWithTitle:NSLocalizedString(@"Unable to create the user phrase file.", @"") content:content confirmButtonTitle:NSLocalizedString(@"OK", @"") cancelButtonTitle:nil cancelAsDefault:NO delegate:nil];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString *path = LTUserPhrasesDataPathCHT();
|
||||||
|
NSLog(@"Open %@", path);
|
||||||
|
if (![[NSFileManager defaultManager] fileExistsAtPath:path]) {
|
||||||
|
[[@"" dataUsingEncoding:NSUTF8StringEncoding] writeToFile:path atomically:YES];
|
||||||
|
}
|
||||||
|
NSURL *url = [NSURL fileURLWithPath:path];
|
||||||
|
[[NSWorkspace sharedWorkspace] openURL:url];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)reloadUserPhrases:(id)sender
|
||||||
|
{
|
||||||
|
NSLog(@"reloadUserPhrases called");
|
||||||
|
LTLoadUserLanguageModelFile();
|
||||||
|
}
|
||||||
|
|
||||||
- (void)showAbout:(id)sender
|
- (void)showAbout:(id)sender
|
||||||
{
|
{
|
||||||
// show the About window, and also make the IME app itself the focus
|
// show the About window, and also make the IME app itself the focus
|
||||||
|
@ -1404,9 +1649,17 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, FastLM &
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void LTLoadLanguageModel()
|
void LTLoadLanguageModel()
|
||||||
{
|
{
|
||||||
LTLoadLanguageModelFile(@"data", gLanguageModelCHT);
|
LTLoadLanguageModelFile(@"data", gLanguageModelCHT);
|
||||||
LTLoadLanguageModelFile(@"data-chs", gLanguageModelCHS);
|
LTLoadLanguageModelFile(@"data-chs", gLanguageModelCHS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LTLoadUserLanguageModelFile()
|
||||||
|
{
|
||||||
|
gUserPhraseLanguageModelCHT.close();
|
||||||
|
bool result = gUserPhraseLanguageModelCHT.open([LTUserPhrasesDataPathCHT() UTF8String]);
|
||||||
|
if (!result) {
|
||||||
|
NSLog(@"Failed opening language model for CHT user phrases.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -19,3 +19,8 @@
|
||||||
"Chinese Conversion" = "Convert zh-TW Kanji to KangXi Variants";
|
"Chinese Conversion" = "Convert zh-TW Kanji to KangXi Variants";
|
||||||
"NotificationSwitchON" = " ON";
|
"NotificationSwitchON" = " ON";
|
||||||
"NotificationSwitchOFF" = " OFF";
|
"NotificationSwitchOFF" = " OFF";
|
||||||
|
"User Phrases" = "User Phrases";
|
||||||
|
"Edit User Phrases" = "Edit User Phrases";
|
||||||
|
"Reload User Phrases" = "Reload User Phrases";
|
||||||
|
"Unable to create the user phrase file." = "Unable to create the user phrase file.";
|
||||||
|
"Please check the permission of at \"%@\"." = "Please check the permission of at \"%@\".";
|
||||||
|
|
|
@ -19,3 +19,8 @@
|
||||||
"Chinese Conversion" = "优先使用康熙繁体字";
|
"Chinese Conversion" = "优先使用康熙繁体字";
|
||||||
"NotificationSwitchON" = " 已启用";
|
"NotificationSwitchON" = " 已启用";
|
||||||
"NotificationSwitchOFF" = " 已停用";
|
"NotificationSwitchOFF" = " 已停用";
|
||||||
|
"User Phrases" = "自订语汇";
|
||||||
|
"Edit User Phrases" = "编辑自订语汇";
|
||||||
|
"Reload User Phrases" = "重载自订语汇";
|
||||||
|
"Unable to create the user phrase file." = "无法创建自订语汇档案。";
|
||||||
|
"Please check the permission of at \"%@\"." = "请检查此处的存取权限:\"%@\".";
|
||||||
|
|
|
@ -19,3 +19,8 @@
|
||||||
"Chinese Conversion" = "優先使用康熙繁體字";
|
"Chinese Conversion" = "優先使用康熙繁體字";
|
||||||
"NotificationSwitchON" = " 已啟用";
|
"NotificationSwitchON" = " 已啟用";
|
||||||
"NotificationSwitchOFF" = " 已停用";
|
"NotificationSwitchOFF" = " 已停用";
|
||||||
|
"User Phrases" = "自訂語彙";
|
||||||
|
"Edit User Phrases" = "編輯自訂語彙";
|
||||||
|
"Reload User Phrases" = "重載自訂語彙";
|
||||||
|
"Unable to create the user phrase file." = "無法創建自訂語彙檔案。";
|
||||||
|
"Please check the permission of at \"%@\"." = "請檢查此處的存取權限:\"%@\".";
|
||||||
|
|
Loading…
Reference in New Issue