diff --git a/Source/Base.lproj/preferences.xib b/Source/Base.lproj/preferences.xib
index 1cb0c9b4..65098960 100644
--- a/Source/Base.lproj/preferences.xib
+++ b/Source/Base.lproj/preferences.xib
@@ -203,7 +203,7 @@
-
+
diff --git a/Source/Engine/Keyboard/EmacsKeyHelper.swift b/Source/Engine/Keyboard/EmacsKeyHelper.swift
new file mode 100644
index 00000000..b5001f4d
--- /dev/null
+++ b/Source/Engine/Keyboard/EmacsKeyHelper.swift
@@ -0,0 +1,53 @@
+//
+// EmacsKeyHelper.swift
+//
+// Copyright (c) 2011-2022 The OpenVanilla Project.
+//
+// Contributors:
+// Weizhong Yang (@zonble) @ OpenVanilla
+//
+// Based on the Syrup Project and the Formosana Library
+// by Lukhnos Liu (@lukhnos).
+//
+// Permission is hereby granted, free of charge, to any person
+// obtaining a copy of this software and associated documentation
+// files (the "Software"), to deal in the Software without
+// restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following
+// conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+// OTHER DEALINGS IN THE SOFTWARE.
+//
+
+import Cocoa
+
+@objc enum vChewingEmacsKey: UInt16 {
+ case none = 0
+ case forward = 6 // F
+ case backward = 2 // B
+ case home = 1 // A
+ case end = 5 // E
+ case delete = 4 // D
+ case nextPage = 22 // V
+}
+
+class EmacsKeyHelper: NSObject {
+ @objc static func detect(charCode: UniChar, flags: NSEvent.ModifierFlags) -> vChewingEmacsKey {
+ if flags.contains(.control) {
+ return vChewingEmacsKey(rawValue: charCode) ?? .none
+ }
+ return .none;
+ }
+}
diff --git a/Source/InputMethodController.h b/Source/InputMethodController.h
index 4649650b..75714bb5 100644
--- a/Source/InputMethodController.h
+++ b/Source/InputMethodController.h
@@ -80,11 +80,5 @@
// current input mode
NSString *_inputMode;
-
- // if Chinese conversion is enabled
- BOOL _chineseConversionEnabled;
-
- // if half-width punctuation is enabled
- BOOL _halfWidthPunctuationEnabled;
}
@end
diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm
index efaa3d66..11912be4 100644
--- a/Source/InputMethodController.mm
+++ b/Source/InputMethodController.mm
@@ -45,8 +45,6 @@
#import "LanguageModelManager.h"
#import "vChewing-Swift.h"
-@import OpenCC;
-
// C++ namespace usages
using namespace std;
using namespace Formosa::Mandarin;
@@ -54,40 +52,7 @@ using namespace Formosa::Gramambular;
using namespace vChewing;
using namespace OpenVanilla;
-// default, min and max candidate list text size
-static const NSInteger kDefaultCandidateListTextSize = 16;
static const NSInteger kMinKeyLabelSize = 10;
-static const NSInteger kMinCandidateListTextSize = 12;
-static const NSInteger kMaxCandidateListTextSize = 196;
-
-// default, min and max composing buffer size (in codepoints)
-// modern Macs can usually work up to 16 codepoints when the builder still
-// walks the grid with good performance; slower Macs (like old PowerBooks)
-// will start to sputter beyond 12; such is the algorithmatic complexity
-// of the Viterbi algorithm used in the builder library (at O(N^2))
-static const NSInteger kDefaultComposingBufferSize = 10;
-static const NSInteger kMinComposingBufferSize = 4;
-static const NSInteger kMaxComposingBufferSize = 20;
-
-// user defaults (app perferences) key names; in this project we use
-// NSUserDefaults throughout and do not wrap them in another config object
-static NSString *const kKeyboardLayoutPreferenceKey = @"KeyboardLayout";
-static NSString *const kBasisKeyboardLayoutPreferenceKey = @"BasisKeyboardLayout"; // alphanumeric ("ASCII") input basis
-static NSString *const kFunctionKeyKeyboardLayoutPreferenceKey = @"FunctionKeyKeyboardLayout"; // alphanumeric ("ASCII") input basis
-static NSString *const kFunctionKeyKeyboardLayoutOverrideIncludeShiftKey = @"FunctionKeyKeyboardLayoutOverrideIncludeShift"; // whether include shift
-static NSString *const kCandidateListTextSizeKey = @"CandidateListTextSize";
-static NSString *const kSelectPhraseAfterCursorAsCandidatePreferenceKey = @"SelectPhraseAfterCursorAsCandidate";
-static NSString *const kUseHorizontalCandidateListPreferenceKey = @"UseHorizontalCandidateList";
-static NSString *const kComposingBufferSizePreferenceKey = @"ComposingBufferSize";
-static NSString *const kChooseCandidateUsingSpaceKey = @"ChooseCandidateUsingSpaceKey";
-static NSString *const kChineseConversionEnabledKey = @"ChineseConversionEnabledKey";
-static NSString *const kHalfWidthPunctuationEnabledKey = @"HalfWidthPunctuationEnabledKey";
-static NSString *const kEscToCleanInputBufferKey = @"EscToCleanInputBufferKey";
-
-// advanced (usually optional) settings
-static NSString *const kCandidateTextFontName = @"CandidateTextFontName";
-static NSString *const kCandidateKeyLabelFontName = @"CandidateKeyLabelFontName";
-static NSString *const kCandidateKeys = @"CandidateKeys";
// input modes
static NSString *const kBopomofoModeIdentifier = @"org.openvanilla.inputmethod.vChewing.Bopomofo";
@@ -107,16 +72,6 @@ enum {
kDeleteKeyCode = 117
};
-typedef NS_ENUM(NSUInteger, vChewingEmacsKey) {
- vChewingEmacsKeyNone,
- vChewingEmacsKeyForward,
- vChewingEmacsKeyBackward,
- vChewingEmacsKeyHome,
- vChewingEmacsKeyEnd,
- vChewingEmacsKeyDelete,
- vChewingEmacsKeyNextPage,
-};
-
VTCandidateController *gCurrentCandidateController = nil;
// if DEBUG is defined, a DOT file (GraphViz format) will be written to the
@@ -131,12 +86,6 @@ static inline NSString *LocalizationNotNeeded(NSString *s) {
return s;
}
-// private methods
-@interface vChewingInputMethodController ()
-+ (VTHorizontalCandidateController *)horizontalCandidateController;
-+ (VTVerticalCandidateController *)verticalCandidateController;
-@end
-
@interface vChewingInputMethodController (VTCandidateController)
@end
@@ -169,7 +118,7 @@ static double FindHighestScore(const vector& nodes, double epsilon)
if (_bpmfReadingBuffer) {
delete _bpmfReadingBuffer;
}
-
+
if (_builder) {
delete _builder;
}
@@ -181,31 +130,29 @@ static double FindHighestScore(const vector& nodes, double epsilon)
{
// an instance is initialized whenever a text input client (a Mac app) requires
// text input from an IME
-
+
self = [super initWithServer:server delegate:delegate client:client];
if (self) {
_candidates = [[NSMutableArray alloc] init];
-
+
// create the reading buffer
_bpmfReadingBuffer = new BopomofoReadingBuffer(BopomofoKeyboardLayout::StandardLayout());
-
+
// create the lattice builder
_languageModel = [LanguageModelManager languageModelBopomofo];
_userOverrideModel = [LanguageModelManager userOverrideModel];
-
+
_builder = new BlockReadingBuilder(_languageModel);
-
+
// each Mandarin syllable is separated by a hyphen
_builder->setJoinSeparator("-");
-
+
// create the composing buffer
_composingBuffer = [[NSMutableString alloc] init];
-
+
_inputMode = kBopomofoModeIdentifier;
- _chineseConversionEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:kChineseConversionEnabledKey];
- _halfWidthPunctuationEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:kHalfWidthPunctuationEnabledKey];
}
-
+
return self;
}
@@ -213,18 +160,18 @@ static double FindHighestScore(const vector& nodes, double epsilon)
{
// a menu instance (autoreleased) is requested every time the user click on the input menu
NSMenu *menu = [[NSMenu alloc] initWithTitle:LocalizationNotNeeded(@"Input Method Menu")];
- NSMenuItem *preferenceMenuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"vChewing Preferences", @"") action:@selector(showPreferences:) keyEquivalent:@""];
- [menu addItem:preferenceMenuItem];
-
+
+ [menu addItemWithTitle:NSLocalizedString(@"vChewing Preferences", @"") action:@selector(showPreferences:) keyEquivalent:@""];
+
NSMenuItem *chineseConversionMenuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Chinese Conversion", @"") action:@selector(toggleChineseConverter:) keyEquivalent:@"K"];
chineseConversionMenuItem.keyEquivalentModifierMask = NSEventModifierFlagCommand | NSEventModifierFlagControl;
- chineseConversionMenuItem.state = _chineseConversionEnabled ? NSControlStateValueOn : NSControlStateValueOff;
+ chineseConversionMenuItem.state = Preferences.chineseConversionEnabled ? NSControlStateValueOn : NSControlStateValueOff;
[menu addItem:chineseConversionMenuItem];
-
+
NSMenuItem *halfWidthPunctuationMenuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Use Half-Width Punctuations", @"") action:@selector(toggleHalfWidthPunctuation:) keyEquivalent:@""];
- halfWidthPunctuationMenuItem.state = _halfWidthPunctuationEnabled ? NSControlStateValueOn : NSControlStateValueOff;
+ halfWidthPunctuationMenuItem.state = Preferences.halfWidthPunctuationEnabled ? NSControlStateValueOn : NSControlStateValueOff;
[menu addItem:halfWidthPunctuationMenuItem];
-
+
[menu addItem:[NSMenuItem separatorItem]]; // ------------------------------
if (_inputMode == kSimpBopomofoModeIdentifier) {
@@ -232,23 +179,15 @@ static double FindHighestScore(const vector& nodes, double epsilon)
[menu addItem:editExcludedPhrasesItem];
}
else {
- NSMenuItem *editUserPhrasesItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Edit User Phrases", @"") action:@selector(openUserPhrases:) keyEquivalent:@""];
- [menu addItem:editUserPhrasesItem];
-
- NSMenuItem *editExcludedPhrasesItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Edit Excluded Phrases", @"") action:@selector(openExcludedPhrasesvChewing:) keyEquivalent:@""];
- [menu addItem:editExcludedPhrasesItem];
+ [menu addItemWithTitle:NSLocalizedString(@"Edit User Phrases", @"") action:@selector(openUserPhrases:) keyEquivalent:@""];
+ [menu addItemWithTitle:NSLocalizedString(@"Edit Excluded Phrases", @"") action:@selector(openExcludedPhrasesvChewing:) keyEquivalent:@""];
}
-
- NSMenuItem *reloadUserPhrasesItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Reload User Phrases", @"") action:@selector(reloadUserPhrases:) keyEquivalent:@""];
- [menu addItem:reloadUserPhrasesItem];
+ [menu addItemWithTitle:NSLocalizedString(@"Reload User Phrases", @"") action:@selector(reloadUserPhrases:) keyEquivalent:@""];
[menu addItem:[NSMenuItem separatorItem]]; // ------------------------------
-
- NSMenuItem *updateCheckItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Check for Updates…", @"") action:@selector(checkForUpdate:) keyEquivalent:@""];
- [menu addItem:updateCheckItem];
-
- NSMenuItem *aboutMenuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"About vChewing…", @"") action:@selector(showAbout:) keyEquivalent:@""];
- [menu addItem:aboutMenuItem];
+
+ [menu addItemWithTitle:NSLocalizedString(@"Check for Updates…", @"") action:@selector(checkForUpdate:) keyEquivalent:@""];
+ [menu addItemWithTitle:NSLocalizedString(@"About vChewing…", @"") action:@selector(showAbout:) keyEquivalent:@""];
return menu;
}
@@ -257,67 +196,43 @@ static double FindHighestScore(const vector& nodes, double epsilon)
- (void)activateServer:(id)client
{
[[NSUserDefaults standardUserDefaults] synchronize];
-
+
// Override the keyboard layout. Use US if not set.
- NSString *basisKeyboardLayoutID = [[NSUserDefaults standardUserDefaults] stringForKey:kBasisKeyboardLayoutPreferenceKey];
- if (!basisKeyboardLayoutID) {
- basisKeyboardLayoutID = @"com.apple.keylayout.US";
- }
+ NSString *basisKeyboardLayoutID = Preferences.basisKeyboardLayout;
[client overrideKeyboardWithKeyboardNamed:basisKeyboardLayoutID];
-
+
// reset the state
_currentDeferredClient = nil;
_currentCandidateClient = nil;
_builder->clear();
_walkedNodes.clear();
[_composingBuffer setString:@""];
-
+
// checks and populates the default settings
- NSInteger keyboardLayout = [[NSUserDefaults standardUserDefaults] integerForKey:kKeyboardLayoutPreferenceKey];
- switch (keyboardLayout) {
- case 0:
+ switch (Preferences.keyboardLayout) {
+ case KeyboardLayoutStandard:
_bpmfReadingBuffer->setKeyboardLayout(BopomofoKeyboardLayout::StandardLayout());
break;
- case 1:
+ case KeyboardLayoutEten:
_bpmfReadingBuffer->setKeyboardLayout(BopomofoKeyboardLayout::ETenLayout());
break;
- case 2:
+ case KeyboardLayoutHsu:
_bpmfReadingBuffer->setKeyboardLayout(BopomofoKeyboardLayout::HsuLayout());
break;
- case 3:
+ case KeyboardLayoutEten26:
_bpmfReadingBuffer->setKeyboardLayout(BopomofoKeyboardLayout::ETen26Layout());
break;
- case 4:
+ case KeyboardLayoutHanyuPinyin:
_bpmfReadingBuffer->setKeyboardLayout(BopomofoKeyboardLayout::HanyuPinyinLayout());
break;
- case 5:
+ case KeyboardLayoutIBM:
_bpmfReadingBuffer->setKeyboardLayout(BopomofoKeyboardLayout::IBMLayout());
break;
default:
_bpmfReadingBuffer->setKeyboardLayout(BopomofoKeyboardLayout::StandardLayout());
- [[NSUserDefaults standardUserDefaults] setInteger:0 forKey:kKeyboardLayoutPreferenceKey];
+ Preferences.keyboardLayout = KeyboardLayoutStandard;
}
-
- // set the size
- NSInteger textSize = [[NSUserDefaults standardUserDefaults] integerForKey:kCandidateListTextSizeKey];
- NSInteger previousTextSize = textSize;
- if (textSize == 0) {
- textSize = kDefaultCandidateListTextSize;
- }
- else if (textSize < kMinCandidateListTextSize) {
- textSize = kMinCandidateListTextSize;
- }
- else if (textSize > kMaxCandidateListTextSize) {
- textSize = kMaxCandidateListTextSize;
- }
-
- if (textSize != previousTextSize) {
- [[NSUserDefaults standardUserDefaults] setInteger:textSize forKey:kCandidateListTextSizeKey];
- }
- if (![[NSUserDefaults standardUserDefaults] objectForKey:kChooseCandidateUsingSpaceKey]) {
- [[NSUserDefaults standardUserDefaults] setBool:YES forKey:kChooseCandidateUsingSpaceKey];
- }
-
+
[(AppDelegate *)[NSApp delegate] checkForUpdate];
}
@@ -328,17 +243,17 @@ static double FindHighestScore(const vector& nodes, double epsilon)
_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];
}
@@ -346,7 +261,7 @@ static double FindHighestScore(const vector& nodes, double epsilon)
{
NSString *newInputMode;
vChewingLM *newLanguageModel;
-
+
if ([value isKindOfClass:[NSString class]] && [value isEqual:kSimpBopomofoModeIdentifier]) {
newInputMode = kSimpBopomofoModeIdentifier;
newLanguageModel = [LanguageModelManager languageModelSimpBopomofo];
@@ -355,30 +270,27 @@ static double FindHighestScore(const vector& nodes, double epsilon)
newInputMode = kBopomofoModeIdentifier;
newLanguageModel = [LanguageModelManager languageModelBopomofo];
}
-
+
// Only apply the changes if the value is changed
if (![_inputMode isEqualToString:newInputMode]) {
[[NSUserDefaults standardUserDefaults] synchronize];
-
+
// Remember to override the keyboard layout again -- treat this as an activate eventy
- NSString *basisKeyboardLayoutID = [[NSUserDefaults standardUserDefaults] stringForKey:kBasisKeyboardLayoutPreferenceKey];
- if (!basisKeyboardLayoutID) {
- basisKeyboardLayoutID = @"com.apple.keylayout.US";
- }
+ NSString *basisKeyboardLayoutID = Preferences.basisKeyboardLayout;
[sender overrideKeyboardWithKeyboardNamed:basisKeyboardLayoutID];
-
+
_inputMode = newInputMode;
_languageModel = newLanguageModel;
-
+
if (!_bpmfReadingBuffer->isEmpty()) {
_bpmfReadingBuffer->clear();
[self updateClientComposingBuffer:sender];
}
-
+
if ([_composingBuffer length] > 0) {
[self commitComposition:sender];
}
-
+
if (_builder) {
delete _builder;
_builder = new BlockReadingBuilder(_languageModel);
@@ -389,6 +301,13 @@ static double FindHighestScore(const vector& nodes, double epsilon)
#pragma mark - IMKServerInput protocol methods
+- (NSString *)_convertToKangXi:(NSString *)text
+{
+ // return [VXHanConvert convertToSimplifiedFrom:text]; // VXHanConvert 這個引擎有點落後了,不支援詞組轉換、且修改轉換表的過程很麻煩。
+ // OpenCC 引擎別的都還好,就是有點肥。改日換成純 ObjC 的 OpenCC 實現方案。
+ return [OpenCCBridge convertToKangXi:text];
+}
+
- (void)commitComposition:(id)client
{
// if it's Terminal, we don't commit at the first call (the client of which will not be IPMDServerClientWrapper)
@@ -400,14 +319,14 @@ static double FindHighestScore(const vector& nodes, double epsilon)
}
return;
}
-
+
// Chinese conversion.
NSString *buffer = _composingBuffer;
- BOOL chineseConversionEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:kChineseConversionEnabledKey];
- if (chineseConversionEnabled) {
- buffer = [OpenCCBridge convert:_composingBuffer];
+
+ if (Preferences.chineseConversionEnabled) {
+ buffer = [self _convertToKangXi:_composingBuffer];
}
-
+
// commit the text, clear the state
[client insertText:buffer replacementRange:NSMakeRange(NSNotFound, NSNotFound)];
_builder->clear();
@@ -415,7 +334,6 @@ static double FindHighestScore(const vector& nodes, double epsilon)
[_composingBuffer setString:@""];
gCurrentCandidateController.visible = NO;
[_candidates removeAllObjects];
-
[self _hideTooltip];
}
@@ -427,13 +345,13 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
{
// "updating the composing buffer" means to request the client to "refresh" the text input buffer
// with our "composing text"
-
+
[_composingBuffer setString:@""];
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
@@ -442,10 +360,10 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
string nodeStr = (*wi).node->currentKeyValue().value;
vector 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"
@@ -465,7 +383,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
}
}
}
-
+
// 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
@@ -474,7 +392,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
NSString *tail = [_composingBuffer substringFromIndex:composedStringCursorIndex];
NSString *composedText = [head stringByAppendingString:[reading stringByAppendingString:tail]];
NSInteger cursorIndex = composedStringCursorIndex + [reading length];
-
+
if (_bpmfReadingBuffer->isEmpty() && _builder->markerCursorIndex() != SIZE_MAX) {
// if there is a marked range, we need to tear the string into three parts.
NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:composedText];
@@ -504,7 +422,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
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)];
@@ -519,19 +437,19 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
// of the best possible Mandarain characters given the input syllables,
// using the Viterbi algorithm implemented in the Gramambular library
Walker walker(&_builder->grid());
-
+
// the reverse walk traces the trellis from the end
_walkedNodes = walker.reverseWalk(_builder->grid().width());
-
+
// then we reverse the nodes so that we get the forward-walked nodes
reverse(_walkedNodes.begin(), _walkedNodes.end());
-
+
// if DEBUG is defined, a GraphViz file is written to kGraphVizOutputfile
#if DEBUG
string dotDump = _builder->grid().dumpDOT();
NSString *dotStr = [NSString stringWithUTF8String:dotDump.c_str()];
NSError *error = nil;
-
+
BOOL __unused success = [dotStr writeToFile:kGraphVizOutputfile atomically:YES encoding:NSUTF8StringEncoding error:&error];
#endif
}
@@ -545,38 +463,23 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
// 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
-
- NSInteger _composingBufferSize = [[NSUserDefaults standardUserDefaults] integerForKey:kComposingBufferSizePreferenceKey];
- NSInteger previousComposingBufferSize = _composingBufferSize;
-
- if (_composingBufferSize == 0) {
- _composingBufferSize = kDefaultComposingBufferSize;
- }
- else if (_composingBufferSize < kMinComposingBufferSize) {
- _composingBufferSize = kMinComposingBufferSize;
- }
- else if (_composingBufferSize > kMaxComposingBufferSize) {
- _composingBufferSize = kMaxComposingBufferSize;
- }
-
- if (_composingBufferSize != previousComposingBufferSize) {
- [[NSUserDefaults standardUserDefaults] setInteger:_composingBufferSize forKey:kComposingBufferSizePreferenceKey];
- }
-
- if (_builder->grid().width() > (size_t)_composingBufferSize) {
+
+ NSInteger composingBufferSize = Preferences.composingBufferSize;
+
+ 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()];
// Chinese conversion.
- BOOL chineseConversionEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:kChineseConversionEnabledKey];
+ BOOL chineseConversionEnabled = Preferences.chineseConversionEnabled;
if (chineseConversionEnabled) {
- popedText = [OpenCCBridge convert:popedText];
+ popedText = [self _convertToKangXi:popedText];
}
[client insertText:popedText replacementRange:NSMakeRange(NSNotFound, NSNotFound)];
_builder->removeHeadReadings(anchor.spanningLength);
}
}
-
+
[self walk];
}
@@ -588,67 +491,19 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
- (string)_currentLayout
{
- string layout = string("Standard_");;
- NSInteger keyboardLayout = [[NSUserDefaults standardUserDefaults] integerForKey:kKeyboardLayoutPreferenceKey];
- switch (keyboardLayout) {
- case 0:
- layout = string("Standard_");
- break;
- case 1:
- layout = string("ETen_");
- break;
- case 2:
- layout = string("ETen26_");
- break;
- case 3:
- layout = string("Hsu_");
- break;
- case 4:
- layout = string("HanyuPinyin_");
- break;
- case 5:
- layout = string("IBM_");
- break;
- default:
- break;
- }
+ NSString *keyboardLayoutName = Preferences.keyboardLayoutName;
+ string layout = string(keyboardLayoutName.UTF8String) + string("_");
return layout;
}
-- (vChewingEmacsKey)_detectEmacsKeyFromCharCode:(UniChar)charCode modifiers:(NSUInteger)flags
-{
- if (flags & NSControlKeyMask) {
- char c = charCode + 'a' - 1;
- if (c == 'a') {
- return vChewingEmacsKeyHome;
- }
- else if (c == 'e') {
- return vChewingEmacsKeyEnd;
- }
- else if (c == 'f') {
- return vChewingEmacsKeyForward;
- }
- else if (c == 'b') {
- return vChewingEmacsKeyBackward;
- }
- else if (c == 'd') {
- return vChewingEmacsKeyDelete;
- }
- else if (c == 'v') {
- return vChewingEmacsKeyNextPage;
- }
- }
- return vChewingEmacsKeyNone;
-}
-
- (BOOL)handleInputText:(NSString*)inputText key:(NSInteger)keyCode modifiers:(NSUInteger)flags client:(id)client
{
NSRect textFrame = NSZeroRect;
NSDictionary *attributes = nil;
-
+
bool composeReading = false;
BOOL useVerticalMode = NO;
-
+
@try {
attributes = [client attributesForCharacterIndex:0 lineHeightRectangle:&textFrame];
useVerticalMode = [attributes objectForKey:@"IMKTextOrientation"] && [[attributes objectForKey:@"IMKTextOrientation"] integerValue] == 0;
@@ -656,35 +511,35 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
@catch (NSException *e) {
// exception may raise while using Twitter.app's search filed.
}
-
+
NSInteger cursorForwardKey = useVerticalMode ? kDownKeyCode : kRightKeyCode;
NSInteger cursorBackwardKey = useVerticalMode ? kUpKeyCode : kLeftKeyCode;
NSInteger extraChooseCandidateKey = useVerticalMode ? kLeftKeyCode : kDownKeyCode;
NSInteger absorbedArrowKey = useVerticalMode ? kRightKeyCode : kUpKeyCode;
NSInteger verticalModeOnlyChooseCandidateKey = useVerticalMode ? absorbedArrowKey : 0;
-
+
// get the unicode character code
UniChar charCode = [inputText length] ? [inputText characterAtIndex:0] : 0;
-
- vChewingEmacsKey emacsKey = [self _detectEmacsKeyFromCharCode:charCode modifiers:flags];
-
+
+ vChewingEmacsKey emacsKey = [EmacsKeyHelper detectWithCharCode:charCode flags:flags];
+
if ([[client bundleIdentifier] isEqualToString:@"com.apple.Terminal"] && [NSStringFromClass([client class]) isEqualToString:@"IPMDServerClientWrapper"]) {
// special handling for com.apple.Terminal
_currentDeferredClient = client;
}
-
+
// if the inputText is empty, it's a function key combination, we ignore it
if (![inputText length]) {
return NO;
}
-
+
// 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 & NSCommandKeyMask) || (flags & NSControlKeyMask) || (flags & NSAlternateKeyMask) || (flags & NSNumericPadKeyMask))) {
return NO;
}
-
+
// Caps Lock processing : if Caps Lock is on, temporarily disable bopomofo.
if (charCode == 8 || charCode == 13 || keyCode == absorbedArrowKey || keyCode == extraChooseCandidateKey || keyCode == cursorForwardKey || keyCode == cursorBackwardKey) {
// do nothing if backspace is pressed -- we ignore the key
@@ -694,40 +549,40 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
if ([_composingBuffer length]) {
[self commitComposition:client];
}
-
+
// first commit everything in the buffer.
if (flags & NSShiftKeyMask) {
return NO;
}
-
+
// if ASCII but not printable, don't use insertText:replacementRange: as many apps don't handle non-ASCII char insertions.
if (charCode < 0x80 && !isprint(charCode)) {
return NO;
}
-
+
// 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)];
return YES;
}
-
+
if (flags & NSNumericPadKeyMask) {
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)];
return YES;
}
}
-
+
// if we have candidate, it means we need to pass the event to the candidate handler
if ([_candidates count]) {
return [self _handleCandidateEventWithInputText:inputText charCode:charCode keyCode:keyCode emacsKey:(vChewingEmacsKey)emacsKey];
}
-
+
// If we have marker index.
if (_builder->markerCursorIndex() != SIZE_MAX) {
// ESC
@@ -747,7 +602,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
[self updateClientComposingBuffer:client];
return YES;
}
- // Shift + Left // Shift + Up in vertical tyinging mode
+ // Shift + left
if ((keyCode == cursorBackwardKey || emacsKey == vChewingEmacsKeyBackward)
&& (flags & NSShiftKeyMask)) {
if (_builder->markerCursorIndex() > 0) {
@@ -759,9 +614,9 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
[self updateClientComposingBuffer:client];
return YES;
}
- // Shift + Right // Shift + Down in vertical tyinging mode
+ // Shift + Right
if ((keyCode == cursorForwardKey || emacsKey == vChewingEmacsKeyForward)
- && (flags & NSShiftKeyMask)) {
+ && (flags & NSShiftKeyMask)) {
if (_builder->markerCursorIndex() < _builder->length()) {
_builder->setMarkerCursorIndex(_builder->markerCursorIndex() + 1);
}
@@ -771,14 +626,14 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
[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);
-
+
// if we have a tone marker, we have to insert the reading to the
// builder in other words, if we don't have a tone marker, we just
// update the composing buffer
@@ -788,55 +643,55 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
return YES;
}
}
-
+
// see if we have composition if Enter/Space is hit and buffer is not empty
// this is bit-OR'ed so that the tone marker key is also taken into account
composeReading |= (!_bpmfReadingBuffer->isEmpty() && (charCode == 32 || charCode == 13));
if (composeReading) {
// combine the reading
string reading = _bpmfReadingBuffer->syllable().composedString();
-
+
// see if we have a unigram for this
if (!_languageModel->hasUnigramsForKey(reading)) {
[self beep];
[self updateClientComposingBuffer:client];
return YES;
}
-
+
// and insert it into the lattice
_builder->insertReadingAtCursor(reading);
-
+
// then walk the lattice
[self popOverflowComposingTextAndWalk:client];
-
+
// get user override model suggestion
string overrideValue = (_inputMode == kSimpBopomofoModeIdentifier) ? "" :
- _userOverrideModel->suggest(_walkedNodes, _builder->cursorIndex(), [[NSDate date] timeIntervalSince1970]);
-
+ _userOverrideModel->suggest(_walkedNodes, _builder->cursorIndex(), [[NSDate date] timeIntervalSince1970]);
+
if (!overrideValue.empty()) {
size_t cursorIndex = [self actualCandidateCursorIndex];
vector nodes = _builder->grid().nodesCrossingOrEndingAt(cursorIndex);
double highestScore = FindHighestScore(nodes, kEpsilon);
_builder->grid().overrideNodeScoreForSelectedCandidate(cursorIndex, overrideValue, highestScore);
}
-
+
// then update the text
_bpmfReadingBuffer->clear();
[self updateClientComposingBuffer:client];
-
+
if (_inputMode == kSimpBopomofoModeIdentifier) {
[self _showCandidateWindowUsingVerticalMode:useVerticalMode client:client];
}
-
+
// and tells the client that the key is consumed
return YES;
}
-
+
// keyCode 125 = Down, charCode 32 = Space
if (_bpmfReadingBuffer->isEmpty() && [_composingBuffer length] > 0 && (keyCode == extraChooseCandidateKey || charCode == 32 || (useVerticalMode && (keyCode == verticalModeOnlyChooseCandidateKey)))) {
if (charCode == 32) {
// if the spacebar is NOT set to be a selection key
- if (![[NSUserDefaults standardUserDefaults] boolForKey:kChooseCandidateUsingSpaceKey]) {
+ if (!Preferences.chooseCandidateUsingSpace) {
if (_builder->cursorIndex() >= _builder->length()) {
[_composingBuffer appendString:@" "];
[self commitComposition:client];
@@ -848,17 +703,17 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
[self updateClientComposingBuffer:client];
}
return YES;
-
+
}
}
[self _showCandidateWindowUsingVerticalMode:useVerticalMode client:client];
return YES;
}
-
+
// Esc
if (charCode == 27) {
- BOOL escToClearInputBufferEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:kEscToCleanInputBufferKey];
-
+ BOOL escToClearInputBufferEnabled = Preferences.escToCleanInputBuffer;
+
if (escToClearInputBufferEnabled) {
// if the optioon is enabled, we clear everythiong including the composing
// buffer, walked nodes and the reading.
@@ -876,10 +731,10 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
// "cancels" the current composed character and revert it to
// Bopomofo reading, in odds with the expectation of users from
// other platforms
-
+
if (_bpmfReadingBuffer->isEmpty()) {
// no nee to beep since the event is deliberately triggered by user
-
+
if (![_composingBuffer length]) {
return NO;
}
@@ -888,11 +743,11 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
_bpmfReadingBuffer->clear();
}
}
-
+
[self updateClientComposingBuffer:client];
return YES;
}
-
+
// handle cursor backward
if (keyCode == cursorBackwardKey || emacsKey == vChewingEmacsKeyBackward) {
if (!_bpmfReadingBuffer->isEmpty()) {
@@ -902,7 +757,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
if (![_composingBuffer length]) {
return NO;
}
-
+
if (flags & NSShiftKeyMask) {
// Shift + left
if (_builder->cursorIndex() > 0) {
@@ -920,11 +775,11 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
}
}
}
-
+
[self updateClientComposingBuffer:client];
return YES;
}
-
+
// handle cursor forward
if (keyCode == cursorForwardKey || emacsKey == vChewingEmacsKeyForward) {
if (!_bpmfReadingBuffer->isEmpty()) {
@@ -934,7 +789,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
if (![_composingBuffer length]) {
return NO;
}
-
+
if (flags & NSShiftKeyMask) {
// Shift + Right
if (_builder->cursorIndex() < _builder->length()) {
@@ -951,11 +806,11 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
}
}
}
-
+
[self updateClientComposingBuffer:client];
return YES;
}
-
+
if (keyCode == kHomeKeyCode || emacsKey == vChewingEmacsKeyHome) {
if (!_bpmfReadingBuffer->isEmpty()) {
[self beep];
@@ -964,7 +819,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
if (![_composingBuffer length]) {
return NO;
}
-
+
if (_builder->cursorIndex()) {
_builder->setCursorIndex(0);
}
@@ -972,11 +827,11 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
[self beep];
}
}
-
+
[self updateClientComposingBuffer:client];
return YES;
}
-
+
if (keyCode == kEndKeyCode || emacsKey == vChewingEmacsKeyEnd) {
if (!_bpmfReadingBuffer->isEmpty()) {
[self beep];
@@ -985,7 +840,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
if (![_composingBuffer length]) {
return NO;
}
-
+
if (_builder->cursorIndex() != _builder->length()) {
_builder->setCursorIndex(_builder->length());
}
@@ -993,11 +848,11 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
[self beep];
}
}
-
+
[self updateClientComposingBuffer:client];
return YES;
}
-
+
if (keyCode == absorbedArrowKey || keyCode == extraChooseCandidateKey) {
if (!_bpmfReadingBuffer->isEmpty()) {
[self beep];
@@ -1005,14 +860,14 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
[self updateClientComposingBuffer:client];
return YES;
}
-
+
// Backspace
if (charCode == 8) {
if (_bpmfReadingBuffer->isEmpty()) {
if (![_composingBuffer length]) {
return NO;
}
-
+
if (_builder->cursorIndex()) {
_builder->deleteReadingBeforeCursor();
[self walk];
@@ -1024,18 +879,18 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
else {
_bpmfReadingBuffer->backspace();
}
-
+
[self updateClientComposingBuffer:client];
return YES;
}
-
+
// Delete
if (keyCode == kDeleteKeyCode || emacsKey == vChewingEmacsKeyDelete) {
if (_bpmfReadingBuffer->isEmpty()) {
if (![_composingBuffer length]) {
return NO;
}
-
+
if (_builder->cursorIndex() != _builder->length()) {
_builder->deleteReadingAfterCursor();
[self walk];
@@ -1047,21 +902,21 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
else {
[self beep];
}
-
+
[self updateClientComposingBuffer:client];
return YES;
}
-
+
// Enter
if (charCode == 13) {
if (![_composingBuffer length]) {
return NO;
}
-
+
[self commitComposition:client];
return YES;
}
-
+
// punctuation list
if ((char)charCode == '`') {
if (_languageModel->hasUnigramsForKey(string("_punctuation_list"))) {
@@ -1077,21 +932,21 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
return YES;
}
}
-
+
// if nothing is matched, see if it's a punctuation key for current layout.
string layout = [self _currentLayout];
- string punctuationNamePrefix = (_halfWidthPunctuationEnabled ? string("_half_punctuation_"): string("_punctuation_"));
+ string punctuationNamePrefix = Preferences.halfWidthPunctuationEnabled ? string("_half_punctuation_"): string("_punctuation_");
string customPunctuation = punctuationNamePrefix + layout + string(1, (char)charCode);
if ([self _handlePunctuation:customPunctuation usingVerticalMode:useVerticalMode client:client]) {
return YES;
}
-
+
// if nothing is matched, see if it's a punctuation key.
string punctuation = punctuationNamePrefix + string(1, (char)charCode);
if ([self _handlePunctuation:punctuation usingVerticalMode:useVerticalMode client:client]) {
return YES;
}
-
+
// still nothing, then we update the composing buffer (some app has
// strange behavior if we don't do this, "thinking" the key is not
// actually consumed)
@@ -1100,7 +955,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
[self updateClientComposingBuffer:client];
return YES;
}
-
+
return NO;
}
@@ -1115,7 +970,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
[self beep];
}
[self updateClientComposingBuffer:client];
-
+
if (_inputMode == kSimpBopomofoModeIdentifier && _bpmfReadingBuffer->isEmpty()) {
[self collectCandidates];
if ([_candidates count] == 1) {
@@ -1136,11 +991,11 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
(charCode == 27) ||
((_inputMode == kSimpBopomofoModeIdentifier) &&
(charCode == 8 || keyCode == kDeleteKeyCode));
-
+
if (cancelCandidateKey) {
gCurrentCandidateController.visible = NO;
[_candidates removeAllObjects];
-
+
if (_inputMode == kSimpBopomofoModeIdentifier) {
_builder->clear();
_walkedNodes.clear();
@@ -1260,12 +1115,12 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
else if (keyCode == kHomeKeyCode || emacsKey == vChewingEmacsKeyHome) {
if (gCurrentCandidateController.selectedCandidateIndex == 0) {
[self beep];
-
+
}
else {
gCurrentCandidateController.selectedCandidateIndex = 0;
}
-
+
[self updateClientComposingBuffer:_currentCandidateClient];
return YES;
}
@@ -1276,7 +1131,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
else {
gCurrentCandidateController.selectedCandidateIndex = [_candidates count] - 1;
}
-
+
[self updateClientComposingBuffer:_currentCandidateClient];
return YES;
}
@@ -1288,7 +1143,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
break;
}
}
-
+
[gCurrentCandidateController.keyLabels indexOfObject:inputText];
if (index != NSNotFound) {
NSUInteger candidateIndex = [gCurrentCandidateController candidateIndexAtKeyLabelIndex:index];
@@ -1297,15 +1152,15 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
return YES;
}
}
-
+
if (_inputMode == kSimpBopomofoModeIdentifier) {
string layout = [self _currentLayout];
string customPunctuation = string("_punctuation_") + layout + string(1, (char)charCode);
string punctuation = string("_punctuation_") + string(1, (char)charCode);
-
+
BOOL shouldAutoSelectCandidate = _bpmfReadingBuffer->isValidKey((char)charCode) || _languageModel->hasUnigramsForKey(customPunctuation) ||
_languageModel->hasUnigramsForKey(punctuation);
-
+
if (shouldAutoSelectCandidate) {
NSUInteger candidateIndex = [gCurrentCandidateController candidateIndexAtKeyLabelIndex:0];
if (candidateIndex != NSUIntegerMax) {
@@ -1314,7 +1169,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
}
}
}
-
+
[self beep];
[self updateClientComposingBuffer:_currentCandidateClient];
return YES;
@@ -1329,34 +1184,27 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
- (BOOL)handleEvent:(NSEvent *)event client:(id)client
{
if ([event type] == NSFlagsChanged) {
- NSString *functionKeyKeyboardLayoutID = [[NSUserDefaults standardUserDefaults] stringForKey:kFunctionKeyKeyboardLayoutPreferenceKey];
- if (!functionKeyKeyboardLayoutID) {
- functionKeyKeyboardLayoutID = @"com.apple.keylayout.US";
- }
-
- NSString *basisKeyboardLayoutID = [[NSUserDefaults standardUserDefaults] stringForKey:kBasisKeyboardLayoutPreferenceKey];
- if (!basisKeyboardLayoutID) {
- basisKeyboardLayoutID = @"com.apple.keylayout.US";
- }
-
+ NSString *functionKeyKeyboardLayoutID = Preferences.functionKeyboardLayout;
+ NSString *basisKeyboardLayoutID = Preferences.basisKeyboardLayout;
+
// If no override is needed, just return NO.
if ([functionKeyKeyboardLayoutID isEqualToString:basisKeyboardLayoutID]) {
return NO;
}
-
+
// Function key pressed.
- BOOL includeShift = [[NSUserDefaults standardUserDefaults] boolForKey:kFunctionKeyKeyboardLayoutOverrideIncludeShiftKey];
+ BOOL includeShift = Preferences.functionKeyKeyboardLayoutOverrideIncludeShiftKey;
if (([event modifierFlags] & ~NSShiftKeyMask) || (([event modifierFlags] & NSShiftKeyMask) && includeShift)) {
// Override the keyboard layout and let the OS do its thing
[client overrideKeyboardWithKeyboardNamed:functionKeyKeyboardLayoutID];
return NO;
}
-
+
// Revert back to the basis layout when the function key is released
[client overrideKeyboardWithKeyboardNamed:basisKeyboardLayoutID];
return NO;
}
-
+
NSString *inputText = [event characters];
NSInteger keyCode = [event keyCode];
NSUInteger flags = [event modifierFlags];
@@ -1399,13 +1247,13 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
{
// returns the candidate
[_candidates removeAllObjects];
-
+
size_t cursorIndex = [self actualCandidateCursorIndex];
vector nodes = _builder->grid().nodesCrossingOrEndingAt(cursorIndex);
-
+
// sort the nodes, so that longer nodes (representing longer phrases) are placed at the top of the candidate list
stable_sort(nodes.begin(), nodes.end(), NodeAnchorDescendingSorter());
-
+
// then use the C++ trick to retrieve the candidates for each node at/crossing the cursor
for (vector::iterator ni = nodes.begin(), ne = nodes.end(); ni != ne; ++ni) {
const vector& candidates = (*ni).node->candidates();
@@ -1418,10 +1266,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
- (size_t)actualCandidateCursorIndex
{
size_t cursorIndex = _builder->cursorIndex();
-
- BOOL candidatePhraseLocatedAfterCursor = [[NSUserDefaults standardUserDefaults] boolForKey:kSelectPhraseAfterCursorAsCandidatePreferenceKey];
-
- if (candidatePhraseLocatedAfterCursor) {
+ if (Preferences.selectPhraseAfterCursorAsCandidate) {
// MS Phonetics IME style, phrase is *after* the cursor, i.e. cursor is always *before* the phrase
if (cursorIndex < _builder->length()) {
++cursorIndex;
@@ -1432,71 +1277,70 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
++cursorIndex;
}
}
-
+
return cursorIndex;
}
- (void)_showCandidateWindowUsingVerticalMode:(BOOL)useVerticalMode client:(id)client
{
// set the candidate panel style
- BOOL useHorizontalCandidateList = [[NSUserDefaults standardUserDefaults] boolForKey:kUseHorizontalCandidateListPreferenceKey];
-
+
if (useVerticalMode) {
gCurrentCandidateController = [vChewingInputMethodController verticalCandidateController];
}
- else if (useHorizontalCandidateList) {
+ else if (Preferences.useHorizontalCandidateList) {
gCurrentCandidateController = [vChewingInputMethodController horizontalCandidateController];
}
else {
gCurrentCandidateController = [vChewingInputMethodController verticalCandidateController];
}
-
+
// set the attributes for the candidate panel (which uses NSAttributedString)
- NSInteger textSize = [[NSUserDefaults standardUserDefaults] integerForKey:kCandidateListTextSizeKey];
-
+ NSInteger textSize = Preferences.candidateListTextSize;
+
NSInteger keyLabelSize = textSize / 2;
if (keyLabelSize < kMinKeyLabelSize) {
keyLabelSize = kMinKeyLabelSize;
}
-
- NSString *ctFontName = [[NSUserDefaults standardUserDefaults] stringForKey:kCandidateTextFontName];
- NSString *klFontName = [[NSUserDefaults standardUserDefaults] stringForKey:kCandidateKeyLabelFontName];
- NSString *ckeys = [[NSUserDefaults standardUserDefaults] stringForKey:kCandidateKeys];
-
+
+ NSString *ctFontName = Preferences.candidateTextFontName;
+ NSString *klFontName = Preferences.candidateKeyLabelFontName;
+ NSString *ckeys = Preferences.candidateKeys;
+
gCurrentCandidateController.keyLabelFont = klFontName ? [NSFont fontWithName:klFontName size:keyLabelSize] : [NSFont systemFontOfSize:keyLabelSize];
gCurrentCandidateController.candidateFont = ctFontName ? [NSFont fontWithName:ctFontName size:textSize] : [NSFont systemFontOfSize:textSize];
-
+
NSMutableArray *keyLabels = [NSMutableArray arrayWithObjects:@"1", @"2", @"3", @"4", @"5", @"6", @"7", @"8", @"9", nil];
-
+
if ([ckeys length] > 1) {
[keyLabels removeAllObjects];
for (NSUInteger i = 0, c = [ckeys length]; i < c; i++) {
[keyLabels addObject:[ckeys substringWithRange:NSMakeRange(i, 1)]];
}
}
-
+
gCurrentCandidateController.keyLabels = keyLabels;
[self collectCandidates];
-
+
if (_inputMode == kSimpBopomofoModeIdentifier && [_candidates count] == 1) {
[self commitComposition:client];
return;
}
-
+
gCurrentCandidateController.delegate = self;
[gCurrentCandidateController reloadData];
-
+
// update the composing text, set the client
[self updateClientComposingBuffer:client];
_currentCandidateClient = client;
-
+
NSRect lineHeightRect = NSMakeRect(0.0, 0.0, 16.0, 16.0);
-
+
NSInteger cursor = _latestReadingCursor;
if (cursor == [_composingBuffer length] && cursor != 0) {
cursor--;
}
-
+
// some apps (e.g. Twitter for Mac's search bar) handle this call incorrectly, hence the try-catch
@try {
[client attributesForCharacterIndex:cursor lineHeightRectangle:&lineHeightRect];
@@ -1504,14 +1348,14 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
@catch (NSException *exception) {
NSLog(@"%@", exception);
}
-
+
if (useVerticalMode) {
[gCurrentCandidateController setWindowTopLeftPoint:NSMakePoint(lineHeightRect.origin.x + lineHeightRect.size.width + 4.0, lineHeightRect.origin.y - 4.0) bottomOutOfScreenAdjustmentHeight:lineHeightRect.size.height + 4.0];
}
else {
[gCurrentCandidateController setWindowTopLeftPoint:NSMakePoint(lineHeightRect.origin.x, lineHeightRect.origin.y - 4.0) bottomOutOfScreenAdjustmentHeight:lineHeightRect.size.height + 4.0];
}
-
+
gCurrentCandidateController.visible = YES;
}
@@ -1525,14 +1369,14 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
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 < 1) {
return @"";
}
-
+
NSRange range = NSMakeRange((NSInteger)begin, (NSInteger)(end - begin));
NSString *selectedText = [_composingBuffer substringWithRange:range];
return selectedText;
@@ -1546,14 +1390,14 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
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 *selectedText = [_composingBuffer substringWithRange:range];
NSMutableString *string = [[NSMutableString alloc] init];
@@ -1574,7 +1418,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
if (![currentMarkedPhrase length]) {
return NO;
}
-
+
return [LanguageModelManager writeUserPhrase:currentMarkedPhrase];
}
@@ -1598,12 +1442,12 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
- (void)_showTooltip:(NSString *)tooltip client:(id)client
{
NSRect lineHeightRect = NSMakeRect(0.0, 0.0, 16.0, 16.0);
-
+
NSInteger cursor = _latestReadingCursor;
if (cursor == [_composingBuffer length] && cursor != 0) {
cursor--;
}
-
+
// some apps (e.g. Twitter for Mac's search bar) handle this call incorrectly, hence the try-catch
@try {
[client attributesForCharacterIndex:cursor lineHeightRectangle:&lineHeightRect];
@@ -1611,7 +1455,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
@catch (NSException *exception) {
NSLog(@"%@", exception);
}
-
+
[[vChewingInputMethodController tooltipController] showTooltip:tooltip atPoint:lineHeightRect.origin];
}
@@ -1647,7 +1491,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
[[NonModalAlertWindowController sharedInstance] showWithTitle:NSLocalizedString(@"Unable to create the user phrase file.", @"") content:content confirmButtonTitle:NSLocalizedString(@"OK", @"") cancelButtonTitle:nil cancelAsDefault:NO delegate:nil];
return NO;
}
-
+
return YES;
}
@@ -1662,25 +1506,21 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
- (void)openUserPhrases:(id)sender
{
- NSLog(@"openUserPhrases called");
[self _openUserFile:[LanguageModelManager userPhrasesDataPathBopomofo]];
}
- (void)openExcludedPhrasesSimpBopomofo:(id)sender
{
- NSLog(@"openExcludedPhrasesSimpBopomofo called");
[self _openUserFile:[LanguageModelManager excludedPhrasesDataPathSimpBopomofo]];
}
- (void)openExcludedPhrasesvChewing:(id)sender
{
- NSLog(@"openExcludedPhrasesvChewing called");
[self _openUserFile:[LanguageModelManager excludedPhrasesDataPathBopomofo]];
}
- (void)reloadUserPhrases:(id)sender
{
- NSLog(@"reloadUserPhrases called");
[LanguageModelManager loadUserPhrasesModel];
}
@@ -1693,16 +1533,16 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
- (void)toggleChineseConverter:(id)sender
{
- _chineseConversionEnabled = !_chineseConversionEnabled;
- [[NSUserDefaults standardUserDefaults] setBool:_chineseConversionEnabled forKey:kChineseConversionEnabledKey];
-
- [NotifierController notifyWithMessage:[NSString stringWithFormat:@"%@%@%@", NSLocalizedString(@"Chinese Conversion", @""), @"\n", _chineseConversionEnabled ? NSLocalizedString(@"NotificationSwitchON", @"") : NSLocalizedString(@"NotificationSwitchOFF", @"")] stay:NO];
+ BOOL chineseConversionEnabled = [Preferences toggleChineseConversionEnabled];
+ [NotifierController notifyWithMessage:[NSString stringWithFormat:@"%@%@%@", NSLocalizedString(@"Chinese Conversion", @""), @"\n", chineseConversionEnabled ? NSLocalizedString(@"NotificationSwitchON", @"") : NSLocalizedString(@"NotificationSwitchOFF", @"")] stay:NO];
}
- (void)toggleHalfWidthPunctuation:(id)sender
{
- _halfWidthPunctuationEnabled = !_halfWidthPunctuationEnabled;
- [[NSUserDefaults standardUserDefaults] setBool:_halfWidthPunctuationEnabled forKey:kHalfWidthPunctuationEnabledKey];
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-result"
+ [Preferences toogleHalfWidthPunctuationEnabled];
+#pragma GCC diagnostic pop
}
@end
@@ -1724,21 +1564,21 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
- (void)candidateController:(VTCandidateController *)controller didSelectCandidateAtIndex:(NSUInteger)index
{
gCurrentCandidateController.visible = NO;
-
+
// candidate selected, override the node with selection
string selectedValue = [[_candidates objectAtIndex:index] UTF8String];
-
+
size_t cursorIndex = [self actualCandidateCursorIndex];
_builder->grid().fixNodeSelectedCandidate(cursorIndex, selectedValue);
if (_inputMode != kSimpBopomofoModeIdentifier) {
_userOverrideModel->observe(_walkedNodes, cursorIndex, selectedValue, [[NSDate date] timeIntervalSince1970]);
}
-
+
[_candidates removeAllObjects];
-
+
[self walk];
[self updateClientComposingBuffer:_currentCandidateClient];
-
+
if (_inputMode == kSimpBopomofoModeIdentifier) {
[self commitComposition:_currentCandidateClient];
return;
diff --git a/Source/OpenCCBridge.swift b/Source/OpenCCBridge.swift
index adba4a52..53994db8 100644
--- a/Source/OpenCCBridge.swift
+++ b/Source/OpenCCBridge.swift
@@ -1,5 +1,5 @@
//
-// PreferencesWindowController.swift
+// OpenCCBridge.swift
//
// Copyright (c) 2011-2022 The OpenVanilla Project.
//
@@ -34,22 +34,23 @@
import Foundation
import OpenCC
-// Since SwiftyLibreCC only provide Swift classes, we create an NSObject subclass
-// in Swift in order to bridge the Swift classes into our Objective-C++ project.
-class OpenCCBridge : NSObject {
+/// A bridge to let Objctive-C code to access SwiftyOpenCC.
+///
+/// Since SwiftyOpenCC only provide Swift classes, we create an NSObject subclass
+/// in Swift in order to bridge the Swift classes into our Objective-C++ project.
+public class OpenCCBridge: NSObject {
private static let shared = OpenCCBridge()
private var converter: ChineseConverter?
-
private override init() {
try? converter = ChineseConverter(options: .twStandardRev)
super.init()
}
- @objc static func convert(_ string: String) -> String? {
+ /// Converts to Simplified Chinese.
+ ///
+ /// - Parameter string: Text in Traditional Chinese.
+ /// - Returns: Text in Simplified Chinese.
+ @objc public static func convertToKangXi(_ string: String) -> String? {
shared.converter?.convert(string)
}
-
- private func convert(_ string: String) -> String? {
- converter?.convert(string)
- }
}
diff --git a/Source/PreferencesModule.swift b/Source/PreferencesModule.swift
new file mode 100644
index 00000000..4e52d9c8
--- /dev/null
+++ b/Source/PreferencesModule.swift
@@ -0,0 +1,289 @@
+//
+// Preferences.swift
+//
+// Copyright (c) 2011-2022 The OpenVanilla Project.
+//
+// Contributors:
+// Weizhong Yang (@zonble) @ OpenVanilla
+//
+// Based on the Syrup Project and the Formosana Library
+// by Lukhnos Liu (@lukhnos).
+//
+// Permission is hereby granted, free of charge, to any person
+// obtaining a copy of this software and associated documentation
+// files (the "Software"), to deal in the Software without
+// restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following
+// conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+// OTHER DEALINGS IN THE SOFTWARE.
+//
+
+import Cocoa
+
+private let kKeyboardLayoutPreferenceKey = "KeyboardLayout"
+private let kBasisKeyboardLayoutPreferenceKey = "BasisKeyboardLayout"; // alphanumeric ("ASCII") input basi
+private let kFunctionKeyKeyboardLayoutPreferenceKey = "FunctionKeyKeyboardLayout"; // alphanumeric ("ASCII") input basi
+private let kFunctionKeyKeyboardLayoutOverrideIncludeShiftKey = "FunctionKeyKeyboardLayoutOverrideIncludeShift"; // whether include shif
+private let kCandidateListTextSizeKey = "CandidateListTextSize"
+private let kSelectPhraseAfterCursorAsCandidatePreferenceKey = "SelectPhraseAfterCursorAsCandidate"
+private let kUseHorizontalCandidateListPreferenceKey = "UseHorizontalCandidateList"
+private let kComposingBufferSizePreferenceKey = "ComposingBufferSize"
+private let kChooseCandidateUsingSpaceKey = "ChooseCandidateUsingSpaceKey"
+private let kChineseConversionEnabledKey = "ChineseConversionEnabled"
+private let kHalfWidthPunctuationEnabledKey = "HalfWidthPunctuationEnable"
+private let kEscToCleanInputBufferKey = "EscToCleanInputBuffer"
+
+private let kCandidateTextFontName = "CandidateTextFontName"
+private let kCandidateKeyLabelFontName = "CandidateKeyLabelFontName"
+private let kCandidateKeys = "CandidateKeys"
+private let kChineseConversionEngineKey = "ChineseConversionEngine"
+
+private let kDefaultCandidateListTextSize: CGFloat = 18
+private let kMinKeyLabelSize: CGFloat = 10
+private let kMinCandidateListTextSize: CGFloat = 12
+private let kMaxCandidateListTextSize: CGFloat = 196
+
+// default, min and max composing buffer size (in codepoints)
+// modern Macs can usually work up to 16 codepoints when the builder still
+// walks the grid with good performance; slower Macs (like old PowerBooks)
+// will start to sputter beyond 12; such is the algorithmatic complexity
+// of the Viterbi algorithm used in the builder library (at O(N^2))
+private let kDefaultComposingBufferSize = 10
+private let kMinComposingBufferSize = 4
+private let kMaxComposingBufferSize = 20
+
+private let kDefaultKeys = "123456789"
+
+// MARK: Property wrappers
+@propertyWrapper
+struct UserDefault {
+ let key: String
+ let defaultValue: Value
+ var container: UserDefaults = .standard
+
+ var wrappedValue: Value {
+ get {
+ return container.object(forKey: key) as? Value ?? defaultValue
+ }
+ set {
+ container.set(newValue, forKey: key)
+ }
+ }
+}
+
+@propertyWrapper
+struct CandidateListTextSize {
+ let key: String
+ let defaultValue: CGFloat = kDefaultCandidateListTextSize
+ lazy var container: UserDefault = {
+ UserDefault(key: key, defaultValue: defaultValue) }()
+
+ var wrappedValue: CGFloat {
+ mutating get {
+ var value = container.wrappedValue
+ if value < kMinCandidateListTextSize {
+ value = kMinCandidateListTextSize
+ } else if value > kMaxCandidateListTextSize {
+ value = kMaxCandidateListTextSize
+ }
+ return value
+ }
+ set {
+ var value = newValue
+ if value < kMinCandidateListTextSize {
+ value = kMinCandidateListTextSize
+ } else if value > kMaxCandidateListTextSize {
+ value = kMaxCandidateListTextSize
+ }
+ container.wrappedValue = value
+ }
+ }
+}
+
+@propertyWrapper
+struct ComposingBufferSize {
+ let key: String
+ let defaultValue: Int = kDefaultComposingBufferSize
+ lazy var container: UserDefault = {
+ UserDefault(key: key, defaultValue: defaultValue) }()
+
+ var wrappedValue: Int {
+ mutating get {
+ let currentValue = container.wrappedValue
+ if currentValue < kMinComposingBufferSize {
+ return kMinComposingBufferSize
+ } else if currentValue > kMaxComposingBufferSize {
+ return kMaxComposingBufferSize
+ }
+ return currentValue
+ }
+ set {
+ var value = newValue
+ if value < kMinComposingBufferSize {
+ value = kMinComposingBufferSize
+ } else if value > kMaxComposingBufferSize {
+ value = kMaxComposingBufferSize
+ }
+ container.wrappedValue = value
+ }
+ }
+}
+
+@propertyWrapper
+struct ComposingKeys {
+ let key: String
+ let defaultValue: String? = kCandidateKeys
+ lazy var container: UserDefault = {
+ UserDefault(key: key, defaultValue: defaultValue) }()
+
+ var wrappedValue: String? {
+ mutating get {
+ let value = container.wrappedValue
+ if let value = value {
+ if value.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
+ return nil
+ }
+ }
+ return value
+ }
+ set {
+ let value = newValue
+ if let value = value {
+ if value.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
+ container.wrappedValue = nil
+ return
+ }
+ }
+ container.wrappedValue = value
+ }
+ }
+}
+
+// MARK: -
+@objc enum KeyboardLayout: Int {
+ case standard
+ case eten
+ case eten26
+ case hsu
+ case hanyuPinyin
+ case IBM
+
+ var name: String {
+ switch (self) {
+ case .standard:
+ return "Standard"
+ case .eten:
+ return "ETen"
+ case .eten26:
+ return "ETen26"
+ case .hsu:
+ return "Hsu"
+ case .hanyuPinyin:
+ return "HanyuPinyin"
+ case .IBM:
+ return "IBM"
+ }
+ }
+}
+
+@objc enum ChineseConversionEngine: Int {
+ case openCC
+ case vxHanConvert
+
+ var name: String {
+ switch (self) {
+ case .openCC:
+ return "OpenCC"
+ case .vxHanConvert:
+ return "VXHanConvert"
+ }
+ }
+}
+
+// MARK: -
+class Preferences: NSObject {
+ @UserDefault(key: kKeyboardLayoutPreferenceKey, defaultValue: 0)
+ @objc static var keyboardLayout: Int
+
+ @objc static var keyboardLayoutName: String {
+ (KeyboardLayout(rawValue: self.keyboardLayout) ?? KeyboardLayout.standard).name
+ }
+
+ @UserDefault(key: kBasisKeyboardLayoutPreferenceKey, defaultValue: "com.apple.keylayout.US")
+ @objc static var basisKeyboardLayout: String
+
+ @UserDefault(key: kFunctionKeyKeyboardLayoutPreferenceKey, defaultValue: "com.apple.keylayout.US")
+ @objc static var functionKeyboardLayout: String
+
+ @UserDefault(key: kFunctionKeyKeyboardLayoutOverrideIncludeShiftKey, defaultValue: false)
+ @objc static var functionKeyKeyboardLayoutOverrideIncludeShiftKey: Bool
+
+ @CandidateListTextSize(key: kCandidateListTextSizeKey)
+ @objc static var candidateListTextSize: CGFloat
+
+ @UserDefault(key: kSelectPhraseAfterCursorAsCandidatePreferenceKey, defaultValue: false)
+ @objc static var selectPhraseAfterCursorAsCandidate: Bool
+
+ @UserDefault(key: kUseHorizontalCandidateListPreferenceKey, defaultValue: false)
+ @objc static var useHorizontalCandidateList: Bool
+
+ @ComposingBufferSize(key: kComposingBufferSizePreferenceKey)
+ @objc static var composingBufferSize: Int
+
+ @UserDefault(key: kChooseCandidateUsingSpaceKey, defaultValue: true)
+ @objc static var chooseCandidateUsingSpace: Bool
+
+ @UserDefault(key: kChineseConversionEnabledKey, defaultValue: false)
+ @objc static var chineseConversionEnabled: Bool
+
+ @objc static func toggleChineseConversionEnabled() -> Bool {
+ chineseConversionEnabled = !chineseConversionEnabled
+ return chineseConversionEnabled
+ }
+
+ @UserDefault(key: kHalfWidthPunctuationEnabledKey, defaultValue: false)
+ @objc static var halfWidthPunctuationEnabled: Bool
+
+ @objc static func toogleHalfWidthPunctuationEnabled() -> Bool {
+ halfWidthPunctuationEnabled = !halfWidthPunctuationEnabled
+ return halfWidthPunctuationEnabled;
+ }
+
+ @UserDefault(key: kEscToCleanInputBufferKey, defaultValue: false)
+ @objc static var escToCleanInputBuffer: Bool
+
+ // MARK: Optional settings
+ @UserDefault(key: kCandidateTextFontName, defaultValue: nil)
+ @objc static var candidateTextFontName: String?
+
+ @UserDefault(key: kCandidateKeyLabelFontName, defaultValue: nil)
+ @objc static var candidateKeyLabelFontName: String?
+
+ @ComposingKeys(key: kCandidateKeys)
+ @objc static var candidateKeys: String?
+
+ @objc static var defaultKeys: String {
+ kDefaultKeys
+ }
+
+ @UserDefault(key: kChineseConversionEngineKey, defaultValue: 0)
+ @objc static var chineseConversionEngine: Int
+
+ @objc static var chineseConversionEngineName: String? {
+ return ChineseConversionEngine(rawValue: chineseConversionEngine)?.name
+ }
+
+}
diff --git a/Source/PreferencesWindowController.swift b/Source/PreferencesWindowController.swift
index a3ce72d7..2cd38ed2 100644
--- a/Source/PreferencesWindowController.swift
+++ b/Source/PreferencesWindowController.swift
@@ -36,9 +36,13 @@
import Cocoa
import Carbon
-private let kBasisKeyboardLayoutPreferenceKey = "BasisKeyboardLayout"
-private let kCandidateKeys = "CandidateKeys"
-private let kDefaultKeys = "123456789"
+// Extend the RangeReplaceableCollection to allow it clean duplicated characters.
+extension RangeReplaceableCollection where Element: Hashable {
+ var charDeDuplicate: Self {
+ var set = Set()
+ return filter{ set.insert($0).inserted }
+ }
+}
// Please note that the class should be exposed as "PreferencesWindowController"
// in Objective-C in order to let IMK to see the same class name as
@@ -47,55 +51,78 @@ private let kDefaultKeys = "123456789"
@IBOutlet weak var fontSizePopUpButton: NSPopUpButton!
@IBOutlet weak var basisKeyboardLayoutButton: NSPopUpButton!
@IBOutlet weak var selectionKeyComboBox: NSComboBox!
-
+
override func awakeFromNib() {
let list = TISCreateInputSourceList(nil, true).takeRetainedValue() as! [TISInputSource]
var usKeyboardLayoutItem: NSMenuItem? = nil
var chosenItem: NSMenuItem? = nil
-
+
basisKeyboardLayoutButton.menu?.removeAllItems()
-
- let basisKeyboardLayoutID = UserDefaults.standard.string(forKey: kBasisKeyboardLayoutPreferenceKey)
+
+ let basisKeyboardLayoutID = Preferences.basisKeyboardLayout
for source in list {
- if let categoryPtr = TISGetInputSourceProperty(source, kTISPropertyInputSourceCategory) {
- let category = Unmanaged.fromOpaque(categoryPtr).takeUnretainedValue()
- if category != kTISCategoryKeyboardInputSource {
+
+ func getString(_ key: CFString) -> String? {
+ if let ptr = TISGetInputSourceProperty(source, key) {
+ return String(Unmanaged.fromOpaque(ptr).takeUnretainedValue())
+ }
+ return nil
+ }
+
+ func getBool(_ key: CFString) -> Bool? {
+ if let ptr = TISGetInputSourceProperty(source, key) {
+ return Unmanaged.fromOpaque(ptr).takeUnretainedValue() == kCFBooleanTrue
+ }
+ return nil
+ }
+
+ if let category = getString(kTISPropertyInputSourceCategory) {
+ if category != String(kTISCategoryKeyboardInputSource) {
continue
}
} else {
continue
}
-
- if let asciiCapablePtr = TISGetInputSourceProperty(source, kTISPropertyInputSourceIsASCIICapable) {
- let asciiCapable = Unmanaged.fromOpaque(asciiCapablePtr).takeUnretainedValue()
- if asciiCapable != kCFBooleanTrue {
+
+ if let asciiCapable = getBool(kTISPropertyInputSourceIsASCIICapable) {
+ if !asciiCapable {
continue
}
} else {
continue
}
-
- if let sourceTypePtr = TISGetInputSourceProperty(source, kTISPropertyInputSourceType) {
- let sourceType = Unmanaged.fromOpaque(sourceTypePtr).takeUnretainedValue()
- if sourceType != kTISTypeKeyboardLayout {
+
+ if let sourceType = getString(kTISPropertyInputSourceType) {
+ if sourceType != String(kTISTypeKeyboardLayout) {
continue
}
} else {
continue
}
-
- guard let sourceIDPtr = TISGetInputSourceProperty(source, kTISPropertyInputSourceID),
- let localizedNamePtr = TISGetInputSourceProperty(source, kTISPropertyLocalizedName) else {
- continue
- }
-
- let sourceID = String(Unmanaged.fromOpaque(sourceIDPtr).takeUnretainedValue())
- let localizedName = String(Unmanaged.fromOpaque(localizedNamePtr).takeUnretainedValue())
-
+
+ guard let sourceID = getString(kTISPropertyInputSourceID),
+ let localizedName = getString(kTISPropertyLocalizedName) else {
+ continue
+ }
+
let menuItem = NSMenuItem()
menuItem.title = localizedName
menuItem.representedObject = sourceID
-
+
+ if let iconPtr = TISGetInputSourceProperty(source, kTISPropertyIconRef) {
+ let icon = IconRef(iconPtr)
+ let image = NSImage(iconRef: icon)
+
+ func resize( _ image: NSImage) -> NSImage {
+ let newImage = NSImage(size: NSSize(width: 16, height: 16))
+ newImage.lockFocus()
+ image.draw(in: NSRect(x: 0, y: 0, width: 16, height: 16))
+ newImage.unlockFocus()
+ return newImage
+ }
+ menuItem.image = resize(image)
+ }
+
if sourceID == "com.apple.keylayout.US" {
usKeyboardLayoutItem = menuItem
}
@@ -104,41 +131,41 @@ private let kDefaultKeys = "123456789"
}
basisKeyboardLayoutButton.menu?.addItem(menuItem)
}
-
+
basisKeyboardLayoutButton.select(chosenItem ?? usKeyboardLayoutItem)
selectionKeyComboBox.usesDataSource = false
- selectionKeyComboBox.addItems(withObjectValues: [kDefaultKeys, "asdfghjkl", "asdfzxcvb"])
-
- var candidateSelectionKeys = (UserDefaults.standard.string(forKey: kCandidateKeys) ?? kDefaultKeys)
- .trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
+ selectionKeyComboBox.removeAllItems()
+ selectionKeyComboBox.addItems(withObjectValues: [Preferences.defaultKeys, "ASDFGHJKL", "ASDFZXCVB"])
+
+ var candidateSelectionKeys = Preferences.candidateKeys ?? Preferences.defaultKeys
if candidateSelectionKeys.isEmpty {
- candidateSelectionKeys = kDefaultKeys
+ candidateSelectionKeys = Preferences.defaultKeys
}
-
+
selectionKeyComboBox.stringValue = candidateSelectionKeys
}
-
- @IBAction func updateBasisKeyboardLayoutAction(_ sender:Any) {
- if let sourceID = basisKeyboardLayoutButton.selectedItem?.representedObject {
- UserDefaults.standard.set(sourceID, forKey: kBasisKeyboardLayoutPreferenceKey)
+
+ @IBAction func updateBasisKeyboardLayoutAction(_ sender: Any) {
+ if let sourceID = basisKeyboardLayoutButton.selectedItem?.representedObject as? String {
+ Preferences.basisKeyboardLayout = sourceID
}
}
-
+
@IBAction func changeSelectionKeyAction(_ sender: Any) {
- let keys = (sender as AnyObject).stringValue.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
+ let keys = (sender as AnyObject).stringValue.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).charDeDuplicate
if keys.count != 9 || !keys.canBeConverted(to: .ascii) {
- selectionKeyComboBox.stringValue = kDefaultKeys
- UserDefaults.standard.removeObject(forKey: kCandidateKeys)
+ selectionKeyComboBox.stringValue = Preferences.defaultKeys
+ Preferences.candidateKeys = nil
NSSound.beep()
return
}
-
+
selectionKeyComboBox.stringValue = keys
- if keys == kDefaultKeys {
- UserDefaults.standard.removeObject(forKey: kCandidateKeys)
+ if keys == Preferences.defaultKeys {
+ Preferences.candidateKeys = nil
} else {
- UserDefaults.standard.set(keys, forKey: kCandidateKeys)
+ Preferences.candidateKeys = keys
}
}
-
+
}
diff --git a/Source/en.lproj/Localizable.strings b/Source/en.lproj/Localizable.strings
index f2c3bf79..c4f74b43 100644
--- a/Source/en.lproj/Localizable.strings
+++ b/Source/en.lproj/Localizable.strings
@@ -17,8 +17,8 @@
"Visit Website" = "Visit Website";
"You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?%@" = "You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?%@";
"Chinese Conversion" = "Convert zh-TW Kanji to KangXi Variants";
-"NotificationSwitchON" = " ON";
-"NotificationSwitchOFF" = " OFF";
+"NotificationSwitchON" = "✔ ON";
+"NotificationSwitchOFF" = "✘ OFF";
"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.";
diff --git a/Source/zh-Hans.lproj/Localizable.strings b/Source/zh-Hans.lproj/Localizable.strings
index 9bd59dd9..8f7bae55 100644
--- a/Source/zh-Hans.lproj/Localizable.strings
+++ b/Source/zh-Hans.lproj/Localizable.strings
@@ -17,8 +17,8 @@
"Visit Website" = "前往网站";
"You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?%@" = "目前使用的威注音官方版本是 %1$@ (%2$@),网路上有更新版本 %3$@ (%4$@) 可供下载。是否要前往威注音网站下载新版来安装?%5$@";
"Chinese Conversion" = "优先使用康熙繁体字";
-"NotificationSwitchON" = " 已启用";
-"NotificationSwitchOFF" = " 已停用";
+"NotificationSwitchON" = "✔ 已启用";
+"NotificationSwitchOFF" = "✘ 已停用";
"Edit User Phrases" = "编辑自订语汇";
"Reload User Phrases" = "重载自订语汇";
"Unable to create the user phrase file." = "无法创建自订语汇档案。";
diff --git a/Source/zh-Hant.lproj/Localizable.strings b/Source/zh-Hant.lproj/Localizable.strings
index 2f426952..bdc29ed1 100644
--- a/Source/zh-Hant.lproj/Localizable.strings
+++ b/Source/zh-Hant.lproj/Localizable.strings
@@ -17,8 +17,8 @@
"Visit Website" = "前往網站";
"You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?%@" = "目前使用的威注音官方版本是 %1$@ (%2$@),網路上有更新版本 %3$@ (%4$@) 可供下載。是否要前往威注音網站下載新版來安裝?%5$@";
"Chinese Conversion" = "優先使用康熙繁體字";
-"NotificationSwitchON" = " 已啟用";
-"NotificationSwitchOFF" = " 已停用";
+"NotificationSwitchON" = "✔ 已啟用";
+"NotificationSwitchOFF" = "✘ 已停用";
"Edit User Phrases" = "編輯自訂語彙";
"Reload User Phrases" = "重載自訂語彙";
"Unable to create the user phrase file." = "無法創建自訂語彙檔案。";
diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj
index b3304943..861ac85c 100644
--- a/vChewing.xcodeproj/project.pbxproj
+++ b/vChewing.xcodeproj/project.pbxproj
@@ -17,6 +17,8 @@
5B5F4F972792A4EA00922DC2 /* UserPhrasesLM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5B5F4F962792A4EA00922DC2 /* UserPhrasesLM.cpp */; };
5BC2D2872793B434002C0BEC /* CMakeLists.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5BC2D2852793B434002C0BEC /* CMakeLists.txt */; };
5BC2D2882793B434002C0BEC /* KeyValueBlobReader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5BC2D2862793B434002C0BEC /* KeyValueBlobReader.cpp */; };
+ 5BC2D28B2793B8FB002C0BEC /* EmacsKeyHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC2D28A2793B8FB002C0BEC /* EmacsKeyHelper.swift */; };
+ 5BC2D28D2793B98F002C0BEC /* PreferencesModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC2D28C2793B98F002C0BEC /* PreferencesModule.swift */; };
5BC3EE1B278FC48C00F5E44C /* VerticalCandidateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC3EE18278FC48C00F5E44C /* VerticalCandidateController.swift */; };
5BC3EE1C278FC48C00F5E44C /* VTCandidateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC3EE19278FC48C00F5E44C /* VTCandidateController.swift */; };
5BC3EE1D278FC48C00F5E44C /* HorizontalCandidateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC3EE1A278FC48C00F5E44C /* HorizontalCandidateController.swift */; };
@@ -105,6 +107,8 @@
5BC2D2842793B434002C0BEC /* KeyValueBlobReader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KeyValueBlobReader.h; sourceTree = ""; };
5BC2D2852793B434002C0BEC /* CMakeLists.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CMakeLists.txt; sourceTree = ""; };
5BC2D2862793B434002C0BEC /* KeyValueBlobReader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = KeyValueBlobReader.cpp; sourceTree = ""; };
+ 5BC2D28A2793B8FB002C0BEC /* EmacsKeyHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmacsKeyHelper.swift; sourceTree = ""; };
+ 5BC2D28C2793B98F002C0BEC /* PreferencesModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesModule.swift; sourceTree = ""; };
5BC3EE18278FC48C00F5E44C /* VerticalCandidateController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerticalCandidateController.swift; sourceTree = ""; };
5BC3EE19278FC48C00F5E44C /* VTCandidateController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VTCandidateController.swift; sourceTree = ""; };
5BC3EE1A278FC48C00F5E44C /* HorizontalCandidateController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HorizontalCandidateController.swift; sourceTree = ""; };
@@ -272,6 +276,14 @@
path = vChewing;
sourceTree = "";
};
+ 5BC2D2892793B8DB002C0BEC /* Keyboard */ = {
+ isa = PBXGroup;
+ children = (
+ 5BC2D28A2793B8FB002C0BEC /* EmacsKeyHelper.swift */,
+ );
+ path = Keyboard;
+ sourceTree = "";
+ };
5BE798A12792E50F00337FF9 /* UI */ = {
isa = PBXGroup;
children = (
@@ -336,7 +348,6 @@
6A0D4F1215FC0EB100ABF4B3 /* Engine */,
6ACA41E715FC1D9000935EF6 /* Installer */,
6A0D4F4715FC0EB900ABF4B3 /* Resources */,
- 5BDF2D052791DA6700838ADB /* AppDelegate.swift */,
5BF4A6FB27844738007DC6E7 /* frmAboutWindow.h */,
5BF4A6FC27844738007DC6E7 /* frmAboutWindow.m */,
6A0D4EC615FC0D6400ABF4B3 /* InputMethodController.h */,
@@ -345,10 +356,12 @@
5B5F4F92279294A300922DC2 /* LanguageModelManager.mm */,
6A0D4EC815FC0D6400ABF4B3 /* main.m */,
6A0D4EF615FC0DA600ABF4B3 /* vChewing-Prefix.pch */,
- 5BDF2D022791C71200838ADB /* NonModalAlertWindowController.swift */,
- 5BDF2D002791C03B00838ADB /* PreferencesWindowController.swift */,
- D427A9C025ED28CC005D43E0 /* OpenCCBridge.swift */,
+ 5BDF2D052791DA6700838ADB /* AppDelegate.swift */,
5BDF2CFD2791BE4400838ADB /* InputSourceHelper.swift */,
+ 5BDF2D022791C71200838ADB /* NonModalAlertWindowController.swift */,
+ D427A9C025ED28CC005D43E0 /* OpenCCBridge.swift */,
+ 5BC2D28C2793B98F002C0BEC /* PreferencesModule.swift */,
+ 5BDF2D002791C03B00838ADB /* PreferencesWindowController.swift */,
D427A9BF25ED28CC005D43E0 /* vChewing-Bridging-Header.h */,
5BA923AC2791B7C20001323A /* vChewingInstaller-Bridging-Header.h */,
);
@@ -380,6 +393,7 @@
6A0D4F1215FC0EB100ABF4B3 /* Engine */ = {
isa = PBXGroup;
children = (
+ 5BC2D2892793B8DB002C0BEC /* Keyboard */,
5BC2D2832793B434002C0BEC /* vChewing */,
5BA8DAFE27928120009C9FFF /* LanguageModel */,
6A0D4F1315FC0EB100ABF4B3 /* Gramambular */,
@@ -688,6 +702,7 @@
buildActionMask = 2147483647;
files = (
5BDF2CFE2791BE4400838ADB /* InputSourceHelper.swift in Sources */,
+ 5BC2D28B2793B8FB002C0BEC /* EmacsKeyHelper.swift in Sources */,
5BE798A72793280C00337FF9 /* NotifierController.swift in Sources */,
5B5F4F93279294A300922DC2 /* LanguageModelManager.mm in Sources */,
6A0D4ED215FC0D6400ABF4B3 /* InputMethodController.mm in Sources */,
@@ -704,6 +719,7 @@
6A0D4F4515FC0EB100ABF4B3 /* Mandarin.cpp in Sources */,
6A0421A815FEF3F50061ED63 /* FastLM.cpp in Sources */,
5BDF2D012791C03B00838ADB /* PreferencesWindowController.swift in Sources */,
+ 5BC2D28D2793B98F002C0BEC /* PreferencesModule.swift in Sources */,
5BC3EE1C278FC48C00F5E44C /* VTCandidateController.swift in Sources */,
5BDF2D032791C71200838ADB /* NonModalAlertWindowController.swift in Sources */,
5BC3EE1D278FC48C00F5E44C /* HorizontalCandidateController.swift in Sources */,