Converts InputMethodController to Swift.
This commit is contained in:
parent
74cd93f6ca
commit
4c358c1c1d
|
@ -8,7 +8,6 @@
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
6A0D4EA715FC0D2D00ABF4B3 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6A0D4EA615FC0D2D00ABF4B3 /* Cocoa.framework */; };
|
6A0D4EA715FC0D2D00ABF4B3 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6A0D4EA615FC0D2D00ABF4B3 /* Cocoa.framework */; };
|
||||||
6A0D4ED215FC0D6400ABF4B3 /* InputMethodController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4EC715FC0D6400ABF4B3 /* InputMethodController.mm */; };
|
|
||||||
6A0D4F0815FC0DA600ABF4B3 /* Bopomofo.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 6A0D4EEF15FC0DA600ABF4B3 /* Bopomofo.tiff */; };
|
6A0D4F0815FC0DA600ABF4B3 /* Bopomofo.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 6A0D4EEF15FC0DA600ABF4B3 /* Bopomofo.tiff */; };
|
||||||
6A0D4F0915FC0DA600ABF4B3 /* Bopomofo@2x.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 6A0D4EF015FC0DA600ABF4B3 /* Bopomofo@2x.tiff */; };
|
6A0D4F0915FC0DA600ABF4B3 /* Bopomofo@2x.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 6A0D4EF015FC0DA600ABF4B3 /* Bopomofo@2x.tiff */; };
|
||||||
6A0D4F4515FC0EB100ABF4B3 /* Mandarin.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F2015FC0EB100ABF4B3 /* Mandarin.cpp */; };
|
6A0D4F4515FC0EB100ABF4B3 /* Mandarin.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F2015FC0EB100ABF4B3 /* Mandarin.cpp */; };
|
||||||
|
@ -59,6 +58,7 @@
|
||||||
D47F7DD3278C1263002F9DD7 /* UserOverrideModel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DD2278C1263002F9DD7 /* UserOverrideModel.cpp */; };
|
D47F7DD3278C1263002F9DD7 /* UserOverrideModel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DD2278C1263002F9DD7 /* UserOverrideModel.cpp */; };
|
||||||
D485D3B92796A8A000657FF3 /* PreferencesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D485D3B82796A8A000657FF3 /* PreferencesTests.swift */; };
|
D485D3B92796A8A000657FF3 /* PreferencesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D485D3B82796A8A000657FF3 /* PreferencesTests.swift */; };
|
||||||
D485D3C02796CE3200657FF3 /* VersionUpdateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D485D3BF2796CE3200657FF3 /* VersionUpdateTests.swift */; };
|
D485D3C02796CE3200657FF3 /* VersionUpdateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D485D3BF2796CE3200657FF3 /* VersionUpdateTests.swift */; };
|
||||||
|
D4A13D5A27A59F0B003BE359 /* InputMethodController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4A13D5927A59D5C003BE359 /* InputMethodController.swift */; };
|
||||||
D4E569DC27A34D0E00AC2CEF /* KeyHandler.mm in Sources */ = {isa = PBXBuildFile; fileRef = D4E569DB27A34CC100AC2CEF /* KeyHandler.mm */; };
|
D4E569DC27A34D0E00AC2CEF /* KeyHandler.mm in Sources */ = {isa = PBXBuildFile; fileRef = D4E569DB27A34CC100AC2CEF /* KeyHandler.mm */; };
|
||||||
D4E569DF27A40F1400AC2CEF /* KeyHandlerBopomofoTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = D4E569DE27A40F1400AC2CEF /* KeyHandlerBopomofoTests.mm */; };
|
D4E569DF27A40F1400AC2CEF /* KeyHandlerBopomofoTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = D4E569DE27A40F1400AC2CEF /* KeyHandlerBopomofoTests.mm */; };
|
||||||
D4E569E027A4123200AC2CEF /* KeyHandlerInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = D456576D279E4F7B00DF6BC9 /* KeyHandlerInput.swift */; };
|
D4E569E027A4123200AC2CEF /* KeyHandlerInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = D456576D279E4F7B00DF6BC9 /* KeyHandlerInput.swift */; };
|
||||||
|
@ -101,8 +101,6 @@
|
||||||
6A0D4EA615FC0D2D00ABF4B3 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
|
6A0D4EA615FC0D2D00ABF4B3 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
|
||||||
6A0D4EA915FC0D2D00ABF4B3 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; };
|
6A0D4EA915FC0D2D00ABF4B3 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; };
|
||||||
6A0D4EAB15FC0D2D00ABF4B3 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
|
6A0D4EAB15FC0D2D00ABF4B3 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
|
||||||
6A0D4EC615FC0D6400ABF4B3 /* InputMethodController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InputMethodController.h; sourceTree = "<group>"; };
|
|
||||||
6A0D4EC715FC0D6400ABF4B3 /* InputMethodController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = InputMethodController.mm; sourceTree = "<group>"; };
|
|
||||||
6A0D4EEF15FC0DA600ABF4B3 /* Bopomofo.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = Bopomofo.tiff; sourceTree = "<group>"; };
|
6A0D4EEF15FC0DA600ABF4B3 /* Bopomofo.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = Bopomofo.tiff; sourceTree = "<group>"; };
|
||||||
6A0D4EF015FC0DA600ABF4B3 /* Bopomofo@2x.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = "Bopomofo@2x.tiff"; sourceTree = "<group>"; };
|
6A0D4EF015FC0DA600ABF4B3 /* Bopomofo@2x.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = "Bopomofo@2x.tiff"; sourceTree = "<group>"; };
|
||||||
6A0D4EF515FC0DA600ABF4B3 /* McBopomofo-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "McBopomofo-Info.plist"; sourceTree = "<group>"; };
|
6A0D4EF515FC0DA600ABF4B3 /* McBopomofo-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "McBopomofo-Info.plist"; sourceTree = "<group>"; };
|
||||||
|
@ -221,6 +219,8 @@
|
||||||
D485D3B62796A8A000657FF3 /* McBopomofoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = McBopomofoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
D485D3B62796A8A000657FF3 /* McBopomofoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = McBopomofoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
D485D3B82796A8A000657FF3 /* PreferencesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesTests.swift; sourceTree = "<group>"; };
|
D485D3B82796A8A000657FF3 /* PreferencesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesTests.swift; sourceTree = "<group>"; };
|
||||||
D485D3BF2796CE3200657FF3 /* VersionUpdateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionUpdateTests.swift; sourceTree = "<group>"; };
|
D485D3BF2796CE3200657FF3 /* VersionUpdateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionUpdateTests.swift; sourceTree = "<group>"; };
|
||||||
|
D495583A27A5C6C4006ADE1C /* LanguageModelManager+Privates.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "LanguageModelManager+Privates.h"; sourceTree = "<group>"; };
|
||||||
|
D4A13D5927A59D5C003BE359 /* InputMethodController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputMethodController.swift; sourceTree = "<group>"; };
|
||||||
D4E569DA27A34CC100AC2CEF /* KeyHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KeyHandler.h; sourceTree = "<group>"; };
|
D4E569DA27A34CC100AC2CEF /* KeyHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KeyHandler.h; sourceTree = "<group>"; };
|
||||||
D4E569DB27A34CC100AC2CEF /* KeyHandler.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = KeyHandler.mm; sourceTree = "<group>"; };
|
D4E569DB27A34CC100AC2CEF /* KeyHandler.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = KeyHandler.mm; sourceTree = "<group>"; };
|
||||||
D4E569DD27A40F1300AC2CEF /* McBopomofoTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "McBopomofoTests-Bridging-Header.h"; sourceTree = "<group>"; };
|
D4E569DD27A40F1300AC2CEF /* McBopomofoTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "McBopomofoTests-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||||
|
@ -308,9 +308,9 @@
|
||||||
6A0D4F1215FC0EB100ABF4B3 /* Engine */,
|
6A0D4F1215FC0EB100ABF4B3 /* Engine */,
|
||||||
6ACA41E715FC1D9000935EF6 /* Installer */,
|
6ACA41E715FC1D9000935EF6 /* Installer */,
|
||||||
6A0D4F4715FC0EB900ABF4B3 /* Resources */,
|
6A0D4F4715FC0EB900ABF4B3 /* Resources */,
|
||||||
6A0D4EC615FC0D6400ABF4B3 /* InputMethodController.h */,
|
D4A13D5927A59D5C003BE359 /* InputMethodController.swift */,
|
||||||
6A0D4EC715FC0D6400ABF4B3 /* InputMethodController.mm */,
|
|
||||||
D41355D6278D7409005E5CBD /* LanguageModelManager.h */,
|
D41355D6278D7409005E5CBD /* LanguageModelManager.h */,
|
||||||
|
D495583A27A5C6C4006ADE1C /* LanguageModelManager+Privates.h */,
|
||||||
D41355D7278D7409005E5CBD /* LanguageModelManager.mm */,
|
D41355D7278D7409005E5CBD /* LanguageModelManager.mm */,
|
||||||
D4E569DA27A34CC100AC2CEF /* KeyHandler.h */,
|
D4E569DA27A34CC100AC2CEF /* KeyHandler.h */,
|
||||||
D4E569DB27A34CC100AC2CEF /* KeyHandler.mm */,
|
D4E569DB27A34CC100AC2CEF /* KeyHandler.mm */,
|
||||||
|
@ -720,12 +720,12 @@
|
||||||
D461B792279DAC010070E734 /* InputState.swift in Sources */,
|
D461B792279DAC010070E734 /* InputState.swift in Sources */,
|
||||||
D47B92C027972AD100458394 /* main.swift in Sources */,
|
D47B92C027972AD100458394 /* main.swift in Sources */,
|
||||||
D44FB74D2792189A003C80A6 /* PhraseReplacementMap.cpp in Sources */,
|
D44FB74D2792189A003C80A6 /* PhraseReplacementMap.cpp in Sources */,
|
||||||
|
D4A13D5A27A59F0B003BE359 /* InputMethodController.swift in Sources */,
|
||||||
D44FB74527915565003C80A6 /* Preferences.swift in Sources */,
|
D44FB74527915565003C80A6 /* Preferences.swift in Sources */,
|
||||||
D4E569DC27A34D0E00AC2CEF /* KeyHandler.mm in Sources */,
|
D4E569DC27A34D0E00AC2CEF /* KeyHandler.mm in Sources */,
|
||||||
D47F7DD0278C0897002F9DD7 /* NonModalAlertWindowController.swift in Sources */,
|
D47F7DD0278C0897002F9DD7 /* NonModalAlertWindowController.swift in Sources */,
|
||||||
D456576E279E4F7B00DF6BC9 /* KeyHandlerInput.swift in Sources */,
|
D456576E279E4F7B00DF6BC9 /* KeyHandlerInput.swift in Sources */,
|
||||||
D47F7DCE278BFB57002F9DD7 /* PreferencesWindowController.swift in Sources */,
|
D47F7DCE278BFB57002F9DD7 /* PreferencesWindowController.swift in Sources */,
|
||||||
6A0D4ED215FC0D6400ABF4B3 /* InputMethodController.mm in Sources */,
|
|
||||||
D41355DB278E6D17005E5CBD /* McBopomofoLM.cpp in Sources */,
|
D41355DB278E6D17005E5CBD /* McBopomofoLM.cpp in Sources */,
|
||||||
D47F7DD3278C1263002F9DD7 /* UserOverrideModel.cpp in Sources */,
|
D47F7DD3278C1263002F9DD7 /* UserOverrideModel.cpp in Sources */,
|
||||||
6A0D4F4515FC0EB100ABF4B3 /* Mandarin.cpp in Sources */,
|
6A0D4F4515FC0EB100ABF4B3 /* Mandarin.cpp in Sources */,
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
// Copyright (c) 2011 and onwards The McBopomofo Authors.
|
|
||||||
//
|
|
||||||
// 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/Cocoa.h>
|
|
||||||
#import <InputMethodKit/InputMethodKit.h>
|
|
||||||
#import "McBopomofo-Swift.h"
|
|
||||||
|
|
||||||
@interface McBopomofoInputMethodController : IMKInputController
|
|
||||||
|
|
||||||
- (void)handleState:(InputState *)newState client:(id)client;
|
|
||||||
|
|
||||||
@end
|
|
|
@ -1,705 +0,0 @@
|
||||||
// Copyright (c) 2011 and onwards The McBopomofo Authors.
|
|
||||||
//
|
|
||||||
// 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 "McBopomofoLM.h"
|
|
||||||
#import "InputMethodController.h"
|
|
||||||
#import "KeyHandler.h"
|
|
||||||
#import "LanguageModelManager.h"
|
|
||||||
|
|
||||||
// Swift Packages
|
|
||||||
@import CandidateUI;
|
|
||||||
@import NotifierUI;
|
|
||||||
@import TooltipUI;
|
|
||||||
@import OpenCCBridge;
|
|
||||||
@import VXHanConvert;
|
|
||||||
|
|
||||||
//// C++ namespace usages
|
|
||||||
using namespace std;
|
|
||||||
using namespace McBopomofo;
|
|
||||||
|
|
||||||
static const NSInteger kMinKeyLabelSize = 10;
|
|
||||||
|
|
||||||
VTCandidateController *gCurrentCandidateController = nil;
|
|
||||||
|
|
||||||
// https://clang-analyzer.llvm.org/faq.html
|
|
||||||
__attribute__((annotate("returns_localized_nsstring")))
|
|
||||||
static inline NSString *LocalizationNotNeeded(NSString *s) {
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
@interface McBopomofoInputMethodController ()
|
|
||||||
{
|
|
||||||
// the current text input client; we need to keep this when candidate panel is on
|
|
||||||
id _currentCandidateClient;
|
|
||||||
|
|
||||||
// a special deferred client for Terminal.app fix
|
|
||||||
id _currentDeferredClient;
|
|
||||||
|
|
||||||
KeyHandler *_keyHandler;
|
|
||||||
InputState *_state;
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
|
|
||||||
@interface McBopomofoInputMethodController (VTCandidateController) <VTCandidateControllerDelegate>
|
|
||||||
@end
|
|
||||||
|
|
||||||
@interface McBopomofoInputMethodController (KeyHandlerDelegate) <KeyHandlerDelegate>
|
|
||||||
@end
|
|
||||||
|
|
||||||
@interface McBopomofoInputMethodController (UI)
|
|
||||||
+ (VTHorizontalCandidateController *)horizontalCandidateController;
|
|
||||||
+ (VTVerticalCandidateController *)verticalCandidateController;
|
|
||||||
+ (TooltipController *)tooltipController;
|
|
||||||
- (void)_showTooltip:(NSString *)tooltip composingBuffer:(NSString *)composingBuffer cursorIndex:(NSInteger)cursorIndex client:(id)client;
|
|
||||||
- (void)_hideTooltip;
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation McBopomofoInputMethodController
|
|
||||||
|
|
||||||
- (id)initWithServer:(IMKServer *)server delegate:(id)delegate client:(id)client
|
|
||||||
{
|
|
||||||
// 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) {
|
|
||||||
_keyHandler = [[KeyHandler alloc] init];
|
|
||||||
_keyHandler.delegate = self;
|
|
||||||
_state = [[InputStateEmpty alloc] init];
|
|
||||||
}
|
|
||||||
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSMenu *)menu
|
|
||||||
{
|
|
||||||
// a menu instance (autoreleased) is requested every time the user click on the input menu
|
|
||||||
NSMenu *menu = [[NSMenu alloc] initWithTitle:LocalizationNotNeeded(@"Input Method Menu")];
|
|
||||||
NSString *inputMode = _keyHandler.inputMode;
|
|
||||||
|
|
||||||
[menu addItemWithTitle:NSLocalizedString(@"McBopomofo Preferences", @"") action:@selector(showPreferences:) keyEquivalent:@""];
|
|
||||||
|
|
||||||
NSMenuItem *chineseConversionMenuItem = [menu addItemWithTitle:NSLocalizedString(@"Chinese Conversion", @"") action:@selector(toggleChineseConverter:) keyEquivalent:@"g"];
|
|
||||||
chineseConversionMenuItem.keyEquivalentModifierMask = NSEventModifierFlagCommand | NSEventModifierFlagControl;
|
|
||||||
chineseConversionMenuItem.state = Preferences.chineseConversionEnabled ? NSControlStateValueOn : NSControlStateValueOff;
|
|
||||||
|
|
||||||
NSMenuItem *halfWidthPunctuationMenuItem = [menu addItemWithTitle:NSLocalizedString(@"Use Half-Width Punctuations", @"") action:@selector(toggleHalfWidthPunctuation:) keyEquivalent:@"h"];
|
|
||||||
halfWidthPunctuationMenuItem.keyEquivalentModifierMask = NSEventModifierFlagCommand | NSEventModifierFlagControl;
|
|
||||||
halfWidthPunctuationMenuItem.state = Preferences.halfWidthPunctuationEnabled ? NSControlStateValueOn : NSControlStateValueOff;
|
|
||||||
|
|
||||||
BOOL optionKeyPressed = [[NSEvent class] respondsToSelector:@selector(modifierFlags)] && ([NSEvent modifierFlags] & NSEventModifierFlagOption);
|
|
||||||
|
|
||||||
if (inputMode == kBopomofoModeIdentifier && optionKeyPressed) {
|
|
||||||
NSMenuItem *phaseReplacementMenuItem = [menu addItemWithTitle:NSLocalizedString(@"Use Phrase Replacement", @"") action:@selector(togglePhraseReplacementEnabled:) keyEquivalent:@""];
|
|
||||||
phaseReplacementMenuItem.state = Preferences.phraseReplacementEnabled ? NSControlStateValueOn : NSControlStateValueOff;
|
|
||||||
}
|
|
||||||
|
|
||||||
[menu addItem:[NSMenuItem separatorItem]];
|
|
||||||
[menu addItemWithTitle:NSLocalizedString(@"User Phrases", @"") action:NULL keyEquivalent:@""];
|
|
||||||
if (inputMode == kPlainBopomofoModeIdentifier) {
|
|
||||||
NSMenuItem *editExcludedPhrasesItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Edit Excluded Phrases", @"") action:@selector(openExcludedPhrasesPlainBopomofo:) keyEquivalent:@""];
|
|
||||||
[menu addItem:editExcludedPhrasesItem];
|
|
||||||
} else {
|
|
||||||
[menu addItemWithTitle:NSLocalizedString(@"Edit User Phrases", @"") action:@selector(openUserPhrases:) keyEquivalent:@""];
|
|
||||||
[menu addItemWithTitle:NSLocalizedString(@"Edit Excluded Phrases", @"") action:@selector(openExcludedPhrasesMcBopomofo:) keyEquivalent:@""];
|
|
||||||
if (optionKeyPressed) {
|
|
||||||
[menu addItemWithTitle:NSLocalizedString(@"Edit Phrase Replacement Table", @"") action:@selector(openPhraseReplacementMcBopomofo:) keyEquivalent:@""];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
[menu addItemWithTitle:NSLocalizedString(@"Reload User Phrases", @"") action:@selector(reloadUserPhrases:) keyEquivalent:@""];
|
|
||||||
[menu addItem:[NSMenuItem separatorItem]];
|
|
||||||
|
|
||||||
[menu addItemWithTitle:NSLocalizedString(@"Check for Updates…", @"") action:@selector(checkForUpdate:) keyEquivalent:@""];
|
|
||||||
[menu addItemWithTitle:NSLocalizedString(@"About McBopomofo…", @"") action:@selector(showAbout:) keyEquivalent:@""];
|
|
||||||
return menu;
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - IMKStateSetting protocol methods
|
|
||||||
|
|
||||||
- (void)activateServer:(id)client
|
|
||||||
{
|
|
||||||
[[NSUserDefaults standardUserDefaults] synchronize];
|
|
||||||
|
|
||||||
// Override the keyboard layout. Use US if not set.
|
|
||||||
NSString *basisKeyboardLayoutID = Preferences.basisKeyboardLayout;
|
|
||||||
[client overrideKeyboardWithKeyboardNamed:basisKeyboardLayoutID];
|
|
||||||
|
|
||||||
// reset the state
|
|
||||||
_currentDeferredClient = nil;
|
|
||||||
_currentCandidateClient = nil;
|
|
||||||
|
|
||||||
[_keyHandler clear];
|
|
||||||
InputStateEmpty *empty = [[InputStateEmpty alloc] init];
|
|
||||||
[self handleState:empty client:client];
|
|
||||||
|
|
||||||
// checks and populates the default settings
|
|
||||||
[_keyHandler syncWithPreferences];
|
|
||||||
[(AppDelegate *) NSApp.delegate checkForUpdate];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)deactivateServer:(id)client
|
|
||||||
{
|
|
||||||
[_keyHandler clear];
|
|
||||||
|
|
||||||
InputStateEmpty *empty = [[InputStateEmpty alloc] init];
|
|
||||||
[self handleState:empty client:client];
|
|
||||||
|
|
||||||
InputStateDeactivated *inactive = [[InputStateDeactivated alloc] init];
|
|
||||||
[self handleState:inactive client:client];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)setValue:(id)value forTag:(long)tag client:(id)sender
|
|
||||||
{
|
|
||||||
NSString *newInputMode;
|
|
||||||
|
|
||||||
if ([value isKindOfClass:[NSString class]] && [value isEqual:kPlainBopomofoModeIdentifier]) {
|
|
||||||
newInputMode = kPlainBopomofoModeIdentifier;
|
|
||||||
} else {
|
|
||||||
newInputMode = kBopomofoModeIdentifier;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only apply the changes if the value is changed
|
|
||||||
if (![_keyHandler.inputMode isEqualToString:newInputMode]) {
|
|
||||||
[[NSUserDefaults standardUserDefaults] synchronize];
|
|
||||||
|
|
||||||
// Remember to override the keyboard layout again -- treat this as an activate event.
|
|
||||||
NSString *basisKeyboardLayoutID = Preferences.basisKeyboardLayout;
|
|
||||||
[sender overrideKeyboardWithKeyboardNamed:basisKeyboardLayoutID];
|
|
||||||
[_keyHandler clear];
|
|
||||||
_keyHandler.inputMode = newInputMode;
|
|
||||||
InputState *empty = [[InputState alloc] init];
|
|
||||||
[self handleState:empty client:sender];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - IMKServerInput protocol methods
|
|
||||||
|
|
||||||
- (NSUInteger)recognizedEvents:(id)sender
|
|
||||||
{
|
|
||||||
return NSEventMaskKeyDown | NSEventMaskFlagsChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)handleEvent:(NSEvent *)event client:(id)client
|
|
||||||
{
|
|
||||||
if ([event type] == NSEventMaskFlagsChanged) {
|
|
||||||
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 = Preferences.functionKeyKeyboardLayoutOverrideIncludeShiftKey;
|
|
||||||
if ((event.modifierFlags & ~NSEventModifierFlagShift) || ((event.modifierFlags & NSEventModifierFlagShift) && includeShift)) {
|
|
||||||
// Override the keyboard layout and let the OS do its thing
|
|
||||||
[client overrideKeyboardWithKeyboardNamed:functionKeyKeyboardLayoutID];
|
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Revert to the basis layout when the function key is released
|
|
||||||
[client overrideKeyboardWithKeyboardNamed:basisKeyboardLayoutID];
|
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
|
|
||||||
NSRect textFrame = NSZeroRect;
|
|
||||||
NSDictionary *attributes = nil;
|
|
||||||
BOOL useVerticalMode = NO;
|
|
||||||
|
|
||||||
@try {
|
|
||||||
attributes = [client attributesForCharacterIndex:0 lineHeightRectangle:&textFrame];
|
|
||||||
useVerticalMode = attributes[@"IMKTextOrientation"] && [attributes[@"IMKTextOrientation"] integerValue] == 0;
|
|
||||||
}
|
|
||||||
@catch (NSException *e) {
|
|
||||||
// exception may raise while using Twitter.app's search filed.
|
|
||||||
}
|
|
||||||
|
|
||||||
if ([[client bundleIdentifier] isEqualToString:@"com.apple.Terminal"] && [NSStringFromClass([client class]) isEqualToString:@"IPMDServerClientWrapper"]) {
|
|
||||||
// special handling for com.apple.Terminal
|
|
||||||
_currentDeferredClient = client;
|
|
||||||
}
|
|
||||||
|
|
||||||
KeyHandlerInput *input = [[KeyHandlerInput alloc] initWithEvent:event isVerticalMode:useVerticalMode];
|
|
||||||
BOOL result = [_keyHandler handleInput:input state:_state stateCallback:^(InputState *state) {
|
|
||||||
[self handleState:state client:client];
|
|
||||||
} candidateSelectionCallback:^{
|
|
||||||
NSLog(@"candidate window updated.");
|
|
||||||
} errorCallback:^{
|
|
||||||
NSBeep();
|
|
||||||
}];
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#pragma mark - States Handling
|
|
||||||
|
|
||||||
- (NSString *)_convertToSimplifiedChineseIfRequired:(NSString *)text
|
|
||||||
{
|
|
||||||
if (!Preferences.chineseConversionEnabled) {
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Preferences.chineseConversionStyle == 1) {
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Preferences.chineseConversionEngine == 1) {
|
|
||||||
return [VXHanConvert convertToSimplifiedFrom:text];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [OpenCCBridge convertToSimplified:text];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)_commitText:(NSString *)text client:(id)client
|
|
||||||
{
|
|
||||||
NSString *buffer = [self _convertToSimplifiedChineseIfRequired:text];
|
|
||||||
if (!buffer.length) {
|
|
||||||
return;;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if it's Terminal, we don't commit at the first call (the client of which will not be IPMDServerClientWrapper)
|
|
||||||
// then we defer the update in the next runloop round -- so that the composing buffer is not
|
|
||||||
// meaninglessly flushed, an annoying bug in Terminal.app since Mac OS X 10.5
|
|
||||||
if ([[client bundleIdentifier] isEqualToString:@"com.apple.Terminal"] && ![NSStringFromClass([client class]) isEqualToString:@"IPMDServerClientWrapper"]) {
|
|
||||||
if (_currentDeferredClient) {
|
|
||||||
id currentDeferredClient = _currentDeferredClient;
|
|
||||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t) (0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
|
||||||
[currentDeferredClient insertText:buffer replacementRange:NSMakeRange(NSNotFound, NSNotFound)];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
[client insertText:buffer replacementRange:NSMakeRange(NSNotFound, NSNotFound)];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)handleState:(InputState *)newState client:(id)client
|
|
||||||
{
|
|
||||||
// NSLog(@"new state: %@ / current state: %@", newState, _state);
|
|
||||||
|
|
||||||
// We need to set the state to the member variable since the candidate
|
|
||||||
// window need to read the candidates from it.
|
|
||||||
InputState *previous = _state;
|
|
||||||
_state = newState;
|
|
||||||
|
|
||||||
if ([newState isKindOfClass:[InputStateDeactivated class]]) {
|
|
||||||
[self _handleDeactivated:(InputStateDeactivated *) newState previous:previous client:client];
|
|
||||||
} else if ([newState isKindOfClass:[InputStateEmpty class]]) {
|
|
||||||
[self _handleEmpty:(InputStateEmpty *) newState previous:previous client:client];
|
|
||||||
} else if ([newState isKindOfClass:[InputStateEmptyIgnoringPreviousState class]]) {
|
|
||||||
[self _handleEmptyIgnoringPrevious:(InputStateEmptyIgnoringPreviousState *) newState previous:previous client:client];
|
|
||||||
} else if ([newState isKindOfClass:[InputStateCommitting class]]) {
|
|
||||||
[self _handleCommitting:(InputStateCommitting *) newState previous:previous client:client];
|
|
||||||
} else if ([newState isKindOfClass:[InputStateInputting class]]) {
|
|
||||||
[self _handleInputting:(InputStateInputting *) newState previous:previous client:client];
|
|
||||||
} else if ([newState isKindOfClass:[InputStateMarking class]]) {
|
|
||||||
[self _handleMarking:(InputStateMarking *) newState previous:previous client:client];
|
|
||||||
} else if ([newState isKindOfClass:[InputStateChoosingCandidate class]]) {
|
|
||||||
[self _handleChoosingCandidate:(InputStateChoosingCandidate *) newState previous:previous client:client];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)_handleDeactivated:(InputStateDeactivated *)state previous:(InputState *)previous client:(id)client
|
|
||||||
{
|
|
||||||
// commit any residue in the composing buffer
|
|
||||||
if ([previous isKindOfClass:[InputStateInputting class]]) {
|
|
||||||
NSString *buffer = ((InputStateInputting *) previous).composingBuffer;
|
|
||||||
[self _commitText:buffer client:client];
|
|
||||||
}
|
|
||||||
[client setMarkedText:@"" selectionRange:NSMakeRange(0, 0) replacementRange:NSMakeRange(NSNotFound, NSNotFound)];
|
|
||||||
|
|
||||||
_currentDeferredClient = nil;
|
|
||||||
_currentCandidateClient = nil;
|
|
||||||
|
|
||||||
gCurrentCandidateController.delegate = nil;
|
|
||||||
gCurrentCandidateController.visible = NO;
|
|
||||||
[self _hideTooltip];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)_handleEmpty:(InputStateEmpty *)state previous:(InputState *)previous client:(id)client
|
|
||||||
{
|
|
||||||
// commit any residue in the composing buffer
|
|
||||||
if ([previous isKindOfClass:[InputStateInputting class]]) {
|
|
||||||
NSString *buffer = ((InputStateInputting *) previous).composingBuffer;
|
|
||||||
[self _commitText:buffer client:client];
|
|
||||||
}
|
|
||||||
|
|
||||||
[client setMarkedText:@"" selectionRange:NSMakeRange(0, 0) replacementRange:NSMakeRange(NSNotFound, NSNotFound)];
|
|
||||||
gCurrentCandidateController.visible = NO;
|
|
||||||
[self _hideTooltip];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)_handleEmptyIgnoringPrevious:(InputStateEmptyIgnoringPreviousState *)state previous:(InputState *)previous client:(id)client
|
|
||||||
{
|
|
||||||
[client setMarkedText:@"" selectionRange:NSMakeRange(0, 0) replacementRange:NSMakeRange(NSNotFound, NSNotFound)];
|
|
||||||
gCurrentCandidateController.visible = NO;
|
|
||||||
[self _hideTooltip];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)_handleCommitting:(InputStateCommitting *)state previous:(InputState *)previous client:(id)client
|
|
||||||
{
|
|
||||||
NSString *poppedText = state.poppedText;
|
|
||||||
[self _commitText:poppedText client:client];
|
|
||||||
gCurrentCandidateController.visible = NO;
|
|
||||||
[self _hideTooltip];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)_handleInputting:(InputStateInputting *)state previous:(InputState *)previous client:(id)client
|
|
||||||
{
|
|
||||||
NSString *poppedText = state.poppedText;
|
|
||||||
if (poppedText.length) {
|
|
||||||
[self _commitText:poppedText client:client];
|
|
||||||
}
|
|
||||||
|
|
||||||
NSUInteger cursorIndex = state.cursorIndex;
|
|
||||||
NSAttributedString *attrString = state.attributedString;
|
|
||||||
|
|
||||||
// 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)];
|
|
||||||
|
|
||||||
gCurrentCandidateController.visible = NO;
|
|
||||||
[self _hideTooltip];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)_handleMarking:(InputStateMarking *)state previous:(InputState *)previous client:(id)client
|
|
||||||
{
|
|
||||||
NSUInteger cursorIndex = state.cursorIndex;
|
|
||||||
NSAttributedString *attrString = state.attributedString;
|
|
||||||
|
|
||||||
// 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)];
|
|
||||||
|
|
||||||
gCurrentCandidateController.visible = NO;
|
|
||||||
if (state.tooltip.length) {
|
|
||||||
[self _showTooltip:state.tooltip composingBuffer:state.composingBuffer cursorIndex:state.markerIndex client:client];
|
|
||||||
} else {
|
|
||||||
[self _hideTooltip];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)_handleChoosingCandidate:(InputStateChoosingCandidate *)state previous:(InputState *)previous client:(id)client
|
|
||||||
{
|
|
||||||
NSUInteger cursorIndex = state.cursorIndex;
|
|
||||||
NSAttributedString *attrString = state.attributedString;
|
|
||||||
|
|
||||||
// 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)];
|
|
||||||
|
|
||||||
if (![previous isKindOfClass:[InputStateChoosingCandidate class]]) {
|
|
||||||
[self _showCandidateWindowWithState:state client:client];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)_showCandidateWindowWithState:(InputStateChoosingCandidate *)state client:(id)client
|
|
||||||
{
|
|
||||||
// set the candidate panel style
|
|
||||||
BOOL useVerticalMode = state.useVerticalMode;
|
|
||||||
|
|
||||||
if (useVerticalMode) {
|
|
||||||
gCurrentCandidateController = [McBopomofoInputMethodController verticalCandidateController];
|
|
||||||
} else if (Preferences.useHorizontalCandidateList) {
|
|
||||||
gCurrentCandidateController = [McBopomofoInputMethodController horizontalCandidateController];
|
|
||||||
} else {
|
|
||||||
gCurrentCandidateController = [McBopomofoInputMethodController verticalCandidateController];
|
|
||||||
}
|
|
||||||
|
|
||||||
// set the attributes for the candidate panel (which uses NSAttributedString)
|
|
||||||
NSInteger textSize = Preferences.candidateListTextSize;
|
|
||||||
|
|
||||||
NSInteger keyLabelSize = textSize / 2;
|
|
||||||
if (keyLabelSize < kMinKeyLabelSize) {
|
|
||||||
keyLabelSize = kMinKeyLabelSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
NSString *ctFontName = Preferences.candidateTextFontName;
|
|
||||||
NSString *klFontName = Preferences.candidateKeyLabelFontName;
|
|
||||||
NSString *candidateKeys = 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 = [@[@"1", @"2", @"3", @"4", @"5", @"6", @"7", @"8", @"9"] mutableCopy];
|
|
||||||
|
|
||||||
if (candidateKeys.length > 1) {
|
|
||||||
[keyLabels removeAllObjects];
|
|
||||||
for (NSUInteger i = 0, c = candidateKeys.length; i < c; i++) {
|
|
||||||
[keyLabels addObject:[candidateKeys substringWithRange:NSMakeRange(i, 1)]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
gCurrentCandidateController.keyLabels = keyLabels;
|
|
||||||
gCurrentCandidateController.delegate = self;
|
|
||||||
[gCurrentCandidateController reloadData];
|
|
||||||
_currentCandidateClient = client;
|
|
||||||
|
|
||||||
NSRect lineHeightRect = NSMakeRect(0.0, 0.0, 16.0, 16.0);
|
|
||||||
NSInteger cursor = state.cursorIndex;
|
|
||||||
if (cursor == state.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];
|
|
||||||
}
|
|
||||||
@catch (NSException *exception) {
|
|
||||||
NSLog(@"lineHeightRectangle %@", 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - Misc menu items
|
|
||||||
|
|
||||||
- (void)showPreferences:(id)sender
|
|
||||||
{
|
|
||||||
// show the preferences panel, and also make the IME app itself the focus
|
|
||||||
if ([IMKInputController instancesRespondToSelector:@selector(showPreferences:)]) {
|
|
||||||
[super showPreferences:sender];
|
|
||||||
} else {
|
|
||||||
[(AppDelegate *) NSApp.delegate showPreferences];
|
|
||||||
}
|
|
||||||
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)toggleChineseConverter:(id)sender
|
|
||||||
{
|
|
||||||
BOOL enabled = [Preferences toggleChineseConversionEnabled];
|
|
||||||
[NotifierController notifyWithMessage:enabled ? NSLocalizedString(@"Chinese conversion on", @"") : NSLocalizedString(@"Chinese conversion off", @"") stay:NO];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)toggleHalfWidthPunctuation:(id)sender
|
|
||||||
{
|
|
||||||
BOOL enabled = [Preferences toggleHalfWidthPunctuationEnabled];
|
|
||||||
[NotifierController notifyWithMessage:enabled ? NSLocalizedString(@"Half-width punctuation on", @"") : NSLocalizedString(@"Half-width punctuation off", @"") stay:NO];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)togglePhraseReplacementEnabled:(id)sender
|
|
||||||
{
|
|
||||||
BOOL enabled = [Preferences togglePhraseReplacementEnabled];
|
|
||||||
McBopomofoLM *lm = [LanguageModelManager languageModelMcBopomofo];
|
|
||||||
lm->setPhraseReplacementEnabled(enabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)checkForUpdate:(id)sender
|
|
||||||
{
|
|
||||||
[(AppDelegate *) NSApp.delegate checkForUpdateForced:YES];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)_checkUserFiles
|
|
||||||
{
|
|
||||||
if (![LanguageModelManager checkIfUserLanguageModelFilesExist]) {
|
|
||||||
NSString *content = [NSString stringWithFormat:NSLocalizedString(@"Please check the permission of at \"%@\".", @""), [LanguageModelManager dataFolderPath]];
|
|
||||||
[[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;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)_openUserFile:(NSString *)path
|
|
||||||
{
|
|
||||||
if (![self _checkUserFiles]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
NSURL *url = [NSURL fileURLWithPath:path];
|
|
||||||
[[NSWorkspace sharedWorkspace] openURL:url];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)openUserPhrases:(id)sender
|
|
||||||
{
|
|
||||||
[self _openUserFile:[LanguageModelManager userPhrasesDataPathMcBopomofo]];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)openExcludedPhrasesPlainBopomofo:(id)sender
|
|
||||||
{
|
|
||||||
[self _openUserFile:[LanguageModelManager excludedPhrasesDataPathPlainBopomofo]];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)openExcludedPhrasesMcBopomofo:(id)sender
|
|
||||||
{
|
|
||||||
[self _openUserFile:[LanguageModelManager excludedPhrasesDataPathMcBopomofo]];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)openPhraseReplacementMcBopomofo:(id)sender
|
|
||||||
{
|
|
||||||
[self _openUserFile:[LanguageModelManager phraseReplacementDataPathMcBopomofo]];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)reloadUserPhrases:(id)sender
|
|
||||||
{
|
|
||||||
[LanguageModelManager loadUserPhrases];
|
|
||||||
[LanguageModelManager loadUserPhraseReplacement];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)showAbout:(id)sender
|
|
||||||
{
|
|
||||||
[[NSApplication sharedApplication] orderFrontStandardAboutPanel:sender];
|
|
||||||
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
#pragma mark -
|
|
||||||
|
|
||||||
@implementation McBopomofoInputMethodController (VTCandidateController)
|
|
||||||
|
|
||||||
- (NSUInteger)candidateCountForController:(VTCandidateController *)controller
|
|
||||||
{
|
|
||||||
if ([_state isKindOfClass:[InputStateChoosingCandidate class]]) {
|
|
||||||
InputStateChoosingCandidate *state = (InputStateChoosingCandidate *) _state;
|
|
||||||
return state.candidates.count;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSString *)candidateController:(VTCandidateController *)controller candidateAtIndex:(NSUInteger)index
|
|
||||||
{
|
|
||||||
if ([_state isKindOfClass:[InputStateChoosingCandidate class]]) {
|
|
||||||
InputStateChoosingCandidate *state = (InputStateChoosingCandidate *) _state;
|
|
||||||
return state.candidates[index];
|
|
||||||
}
|
|
||||||
return @"";
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)candidateController:(VTCandidateController *)controller didSelectCandidateAtIndex:(NSUInteger)index
|
|
||||||
{
|
|
||||||
gCurrentCandidateController.visible = NO;
|
|
||||||
|
|
||||||
if ([_state isKindOfClass:[InputStateChoosingCandidate class]]) {
|
|
||||||
InputStateChoosingCandidate *state = (InputStateChoosingCandidate *) _state;
|
|
||||||
|
|
||||||
// candidate selected, override the node with selection
|
|
||||||
string selectedValue = [state.candidates[index] UTF8String];
|
|
||||||
[_keyHandler fixNodeWithValue:selectedValue];
|
|
||||||
InputStateInputting *inputting = [_keyHandler _buildInputtingState];
|
|
||||||
|
|
||||||
if (_keyHandler.inputMode == kPlainBopomofoModeIdentifier) {
|
|
||||||
[_keyHandler clear];
|
|
||||||
InputStateCommitting *committing = [[InputStateCommitting alloc] initWithPoppedText:inputting.composingBuffer];
|
|
||||||
[self handleState:committing client:_currentCandidateClient];
|
|
||||||
InputStateEmpty *empty = [[InputStateEmpty alloc] init];
|
|
||||||
[self handleState:empty client:_currentCandidateClient];
|
|
||||||
} else {
|
|
||||||
[self handleState:inputting client:_currentCandidateClient];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation McBopomofoInputMethodController (KeyHandlerDelegate)
|
|
||||||
|
|
||||||
- (nonnull VTCandidateController *)candidateControllerForKeyHandler:(nonnull KeyHandler *)keyHandler
|
|
||||||
{
|
|
||||||
return gCurrentCandidateController;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)keyHandler:(nonnull KeyHandler *)keyHandler didRequestWriteUserPhraseWithState:(nonnull InputStateMarking *)state
|
|
||||||
{
|
|
||||||
if (!state.validToWrite) {
|
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
NSString *userPhrase = state.userPhrase;
|
|
||||||
[LanguageModelManager writeUserPhrase:userPhrase];
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)keyHandler:(nonnull KeyHandler *)keyHandler didSelectCandidateAtIndex:(NSInteger)index candidateController:(nonnull VTCandidateController *)controller
|
|
||||||
{
|
|
||||||
[self candidateController:gCurrentCandidateController didSelectCandidateAtIndex:index];
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
|
|
||||||
@implementation McBopomofoInputMethodController (UI)
|
|
||||||
|
|
||||||
+ (VTHorizontalCandidateController *)horizontalCandidateController
|
|
||||||
{
|
|
||||||
static VTHorizontalCandidateController *instance = nil;
|
|
||||||
static dispatch_once_t onceToken;
|
|
||||||
dispatch_once(&onceToken, ^{
|
|
||||||
instance = [[VTHorizontalCandidateController alloc] init];
|
|
||||||
});
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
+ (VTVerticalCandidateController *)verticalCandidateController
|
|
||||||
{
|
|
||||||
static VTVerticalCandidateController *instance = nil;
|
|
||||||
static dispatch_once_t onceToken;
|
|
||||||
dispatch_once(&onceToken, ^{
|
|
||||||
instance = [[VTVerticalCandidateController alloc] init];
|
|
||||||
});
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
+ (TooltipController *)tooltipController
|
|
||||||
{
|
|
||||||
static TooltipController *instance = nil;
|
|
||||||
static dispatch_once_t onceToken;
|
|
||||||
dispatch_once(&onceToken, ^{
|
|
||||||
instance = [[TooltipController alloc] init];
|
|
||||||
});
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)_showTooltip:(NSString *)tooltip composingBuffer:(NSString *)composingBuffer cursorIndex:(NSInteger)cursorIndex client:(id)client
|
|
||||||
{
|
|
||||||
NSRect lineHeightRect = NSMakeRect(0.0, 0.0, 16.0, 16.0);
|
|
||||||
|
|
||||||
NSUInteger cursor = (NSUInteger) cursorIndex;
|
|
||||||
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];
|
|
||||||
}
|
|
||||||
@catch (NSException *exception) {
|
|
||||||
NSLog(@"%@", exception);
|
|
||||||
}
|
|
||||||
|
|
||||||
[[McBopomofoInputMethodController tooltipController] showTooltip:tooltip atPoint:lineHeightRect.origin];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)_hideTooltip
|
|
||||||
{
|
|
||||||
if ([McBopomofoInputMethodController tooltipController].window.isVisible) {
|
|
||||||
[[McBopomofoInputMethodController tooltipController] hide];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
|
@ -0,0 +1,546 @@
|
||||||
|
// Copyright (c) 2022 and onwards The McBopomofo Authors.
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
import InputMethodKit
|
||||||
|
import CandidateUI
|
||||||
|
import NotifierUI
|
||||||
|
import TooltipUI
|
||||||
|
import VXHanConvert
|
||||||
|
import OpenCCBridge
|
||||||
|
|
||||||
|
extension Bool {
|
||||||
|
var state: NSControl.StateValue {
|
||||||
|
self ? .on : .off
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private let kMinKeyLabelSize: CGFloat = 10
|
||||||
|
|
||||||
|
private var gCurrentCandidateController: CandidateController?
|
||||||
|
|
||||||
|
@objc(McBopomofoInputMethodController)
|
||||||
|
class McBopomofoInputMethodController: IMKInputController {
|
||||||
|
|
||||||
|
private static let horizontalCandidateController = HorizontalCandidateController()
|
||||||
|
private static let verticalCandidateController = VerticalCandidateController()
|
||||||
|
private static let tooltipController = TooltipController()
|
||||||
|
|
||||||
|
// MARK: -
|
||||||
|
|
||||||
|
private var currentCandidateClient: Any?
|
||||||
|
private var currentDeferredClient: Any?
|
||||||
|
|
||||||
|
private var keyHandler: KeyHandler = KeyHandler()
|
||||||
|
private var state: InputState = InputState.Empty()
|
||||||
|
|
||||||
|
// MARK: - IMKInputController methods
|
||||||
|
|
||||||
|
override init!(server: IMKServer!, delegate: Any!, client inputClient: Any!) {
|
||||||
|
super.init(server: server, delegate: delegate, client: inputClient)
|
||||||
|
keyHandler.delegate = self
|
||||||
|
}
|
||||||
|
|
||||||
|
override func menu() -> NSMenu! {
|
||||||
|
let menu = NSMenu(title: "Input Method Menu")
|
||||||
|
menu.addItem(withTitle: NSLocalizedString("McBopomofo Preferences", comment: ""), action: #selector(showPreferences(_:)), keyEquivalent: "")
|
||||||
|
|
||||||
|
let chineseConversionItem = menu.addItem(withTitle: NSLocalizedString("Chinese Conversion", comment: ""), action: #selector(toggleChineseConverter(_:)), keyEquivalent: "g")
|
||||||
|
chineseConversionItem.keyEquivalentModifierMask = [.command, .control]
|
||||||
|
chineseConversionItem.state = Preferences.chineseConversionEnabled.state
|
||||||
|
|
||||||
|
let halfWidthPunctuationItem = menu.addItem(withTitle: NSLocalizedString("Use Half-Width Punctuations", comment: ""), action: #selector(toggleHalfWidthPunctuation(_:)), keyEquivalent: "h")
|
||||||
|
halfWidthPunctuationItem.keyEquivalentModifierMask = [.command, .control]
|
||||||
|
halfWidthPunctuationItem.state = Preferences.chineseConversionEnabled.state
|
||||||
|
|
||||||
|
let inputMode = keyHandler.inputMode
|
||||||
|
let optionKeyPressed = NSEvent.modifierFlags.contains(.option)
|
||||||
|
|
||||||
|
if inputMode == kBopomofoModeIdentifier && optionKeyPressed {
|
||||||
|
let phaseReplacementItem = menu.addItem(withTitle: NSLocalizedString("Use Phrase Replacement", comment: ""), action: #selector(togglePhraseReplacementEnabled(_:)), keyEquivalent: "")
|
||||||
|
phaseReplacementItem.state = Preferences.phraseReplacementEnabled.state
|
||||||
|
}
|
||||||
|
|
||||||
|
menu.addItem(NSMenuItem.separator())
|
||||||
|
menu.addItem(withTitle: NSLocalizedString("User Phrases", comment: ""), action: nil, keyEquivalent: "")
|
||||||
|
|
||||||
|
if inputMode == kPlainBopomofoModeIdentifier {
|
||||||
|
menu.addItem(withTitle: NSLocalizedString("Edit Excluded Phrases", comment: ""), action: #selector(openExcludedPhrasesPlainBopomofo(_:)), keyEquivalent: "")
|
||||||
|
} else {
|
||||||
|
menu.addItem(withTitle: NSLocalizedString("Edit User Phrases", comment: ""), action: #selector(openUserPhrases(_:)), keyEquivalent: "")
|
||||||
|
menu.addItem(withTitle: NSLocalizedString("Edit Excluded Phrases", comment: ""), action: #selector(openExcludedPhrasesMcBopomofo(_:)), keyEquivalent: "")
|
||||||
|
if optionKeyPressed {
|
||||||
|
menu.addItem(withTitle: NSLocalizedString("Edit Phrase Replacement Table", comment: ""), action: #selector(openPhraseReplacementMcBopomofo(_:)), keyEquivalent: "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
menu.addItem(withTitle: NSLocalizedString("Reload User Phrases", comment: ""), action: #selector(reloadUserPhrases(_:)), keyEquivalent: "")
|
||||||
|
menu.addItem(NSMenuItem.separator())
|
||||||
|
|
||||||
|
menu.addItem(withTitle: NSLocalizedString("Check for Updates…", comment: ""), action: #selector(checkForUpdate(_:)), keyEquivalent: "")
|
||||||
|
menu.addItem(withTitle: NSLocalizedString("About McBopomofo…", comment: ""), action: #selector(showAbout(_:)), keyEquivalent: "")
|
||||||
|
return menu
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - IMKStateSetting protocol methods
|
||||||
|
|
||||||
|
override func activateServer(_ client: Any!) {
|
||||||
|
UserDefaults.standard.synchronize()
|
||||||
|
|
||||||
|
// Override the keyboard layout. Use US if not set.
|
||||||
|
(client as? IMKTextInput)?.overrideKeyboard(withKeyboardNamed: Preferences.basisKeyboardLayout)
|
||||||
|
// reset the state
|
||||||
|
currentDeferredClient = nil
|
||||||
|
currentCandidateClient = nil
|
||||||
|
|
||||||
|
keyHandler.clear()
|
||||||
|
keyHandler.syncWithPreferences()
|
||||||
|
self.handle(state: .Empty(), client: client)
|
||||||
|
(NSApp.delegate as? AppDelegate)?.checkForUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func deactivateServer(_ sender: Any!) {
|
||||||
|
keyHandler.clear()
|
||||||
|
self.handle(state: .Empty(), client: client)
|
||||||
|
self.handle(state: .Deactivated(), client: client)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func setValue(_ value: Any!, forTag tag: Int, client: Any!) {
|
||||||
|
let newInputMode = value as? String ?? kBopomofoModeIdentifier
|
||||||
|
if keyHandler.inputMode != newInputMode {
|
||||||
|
UserDefaults.standard.synchronize()
|
||||||
|
// Remember to override the keyboard layout again -- treat this as an activate event.
|
||||||
|
(client as? IMKTextInput)?.overrideKeyboard(withKeyboardNamed: Preferences.basisKeyboardLayout)
|
||||||
|
keyHandler.clear()
|
||||||
|
keyHandler.inputMode = newInputMode
|
||||||
|
self.handle(state: .Empty(), client: client)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - - IMKServerInput protocol methods
|
||||||
|
|
||||||
|
override func recognizedEvents(_ sender: Any!) -> Int {
|
||||||
|
let events: NSEvent.EventTypeMask = [.keyDown, .flagsChanged]
|
||||||
|
return Int(events.rawValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func handle(_ event: NSEvent!, client: Any!) -> Bool {
|
||||||
|
if event.type == .flagsChanged {
|
||||||
|
let functionKeyKeyboardLayoutID = Preferences.functionKeyboardLayout
|
||||||
|
let basisKeyboardLayoutID = Preferences.basisKeyboardLayout
|
||||||
|
|
||||||
|
if functionKeyKeyboardLayoutID == basisKeyboardLayoutID {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
let includeShift = Preferences.functionKeyKeyboardLayoutOverrideIncludeShiftKey
|
||||||
|
let notShift = NSEvent.ModifierFlags(rawValue: ~(NSEvent.ModifierFlags.shift.rawValue))
|
||||||
|
if event.modifierFlags.contains(notShift) ||
|
||||||
|
(event.modifierFlags.contains(.shift) && includeShift) {
|
||||||
|
(client as? IMKTextInput)?.overrideKeyboard(withKeyboardNamed: functionKeyKeyboardLayoutID)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
(client as? IMKTextInput)?.overrideKeyboard(withKeyboardNamed: basisKeyboardLayoutID)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var textFrame = NSRect.zero
|
||||||
|
let attributes: [AnyHashable: Any]? = (client as? IMKTextInput)?.attributes(forCharacterIndex: 0, lineHeightRectangle: &textFrame)
|
||||||
|
let useVerticalMode = (attributes?["IMKTextOrientation"] as? NSNumber)?.intValue == 0 || false
|
||||||
|
|
||||||
|
if (client as? IMKTextInput)?.bundleIdentifier() == "com.apple.Terminal" &&
|
||||||
|
NSStringFromClass(client.self as! AnyClass) == "IPMDServerClientWrapper" {
|
||||||
|
currentDeferredClient = client
|
||||||
|
}
|
||||||
|
|
||||||
|
let input = KeyHandlerInput(event: event, isVerticalMode: useVerticalMode)
|
||||||
|
let result = keyHandler.handle(input, state: state) { newState in
|
||||||
|
self.handle(state: newState, client: client)
|
||||||
|
} candidateSelectionCallback: {
|
||||||
|
} errorCallback: {
|
||||||
|
NSSound.beep()
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Menu Items
|
||||||
|
|
||||||
|
@objc override func showPreferences(_ sender: Any?) {
|
||||||
|
super.showPreferences(sender)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func toggleChineseConverter(_ sender: Any?) {
|
||||||
|
let enabled = Preferences.toggleChineseConversionEnabled()
|
||||||
|
NotifierController.notify(message: enabled ? NSLocalizedString("Chinese conversion on", comment: "") : NSLocalizedString("Chinese conversion off", comment: ""))
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func toggleHalfWidthPunctuation(_ sender: Any?) {
|
||||||
|
let enabled = Preferences.togglePhraseReplacementEnabled()
|
||||||
|
NotifierController.notify(message: enabled ? NSLocalizedString("Half-width punctuation on", comment: "") : NSLocalizedString("Half-width punctuation off", comment: ""))
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func togglePhraseReplacementEnabled(_ sender: Any?) {
|
||||||
|
let enabled = Preferences.togglePhraseReplacementEnabled()
|
||||||
|
LanguageModelManager.phraseReplacementEnabled = enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func checkForUpdate(_ sender: Any?) {
|
||||||
|
(NSApp.delegate as? AppDelegate)?.checkForUpdate(forced: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func open(userFileAt path: String) {
|
||||||
|
func checkUserFiles() -> Bool {
|
||||||
|
if !LanguageModelManager.checkIfUserLanguageModelFilesExist() {
|
||||||
|
let content = String(format: NSLocalizedString("Please check the permission of at \"%@\".", comment: ""), LanguageModelManager.dataFolderPath)
|
||||||
|
NonModalAlertWindowController.shared.show(title: NSLocalizedString("Unable to create the user phrase file.", comment: ""), content: content, confirmButtonTitle: NSLocalizedString("OK", comment: ""), cancelButtonTitle: nil, cancelAsDefault: false, delegate: nil)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !checkUserFiles() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let url = URL(fileURLWithPath: path)
|
||||||
|
NSWorkspace.shared.open(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func openUserPhrases(_ sender: Any?) {
|
||||||
|
open(userFileAt: LanguageModelManager.userPhrasesDataPathMcBopomofo)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func openExcludedPhrasesPlainBopomofo(_ sender: Any?) {
|
||||||
|
open(userFileAt: LanguageModelManager.excludedPhrasesDataPathPlainBopomofo)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func openExcludedPhrasesMcBopomofo(_ sender: Any?) {
|
||||||
|
open(userFileAt: LanguageModelManager.excludedPhrasesDataPathMcBopomofo)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func openPhraseReplacementMcBopomofo(_ sender: Any?) {
|
||||||
|
open(userFileAt: LanguageModelManager.phraseReplacementDataPathMcBopomofo)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func reloadUserPhrases(_ sender: Any?) {
|
||||||
|
LanguageModelManager.loadUserPhrases()
|
||||||
|
LanguageModelManager.loadUserPhraseReplacement()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func showAbout(_ sender: Any?) {
|
||||||
|
NSApp.orderFrontStandardAboutPanel(sender)
|
||||||
|
NSApp.activate(ignoringOtherApps: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - State Handling
|
||||||
|
|
||||||
|
extension McBopomofoInputMethodController {
|
||||||
|
|
||||||
|
private func handle(state newState: InputState, client: Any?) {
|
||||||
|
let previous = state
|
||||||
|
state = newState
|
||||||
|
|
||||||
|
if let newState = newState as? InputState.Deactivated {
|
||||||
|
handle(state: newState, previous: previous, client: client)
|
||||||
|
} else if let newState = newState as? InputState.Empty {
|
||||||
|
handle(state: newState, previous: previous, client: client)
|
||||||
|
} else if let newState = newState as? InputState.EmptyIgnoringPreviousState {
|
||||||
|
handle(state: newState, previous: previous, client: client)
|
||||||
|
} else if let newState = newState as? InputState.Committing {
|
||||||
|
handle(state: newState, previous: previous, client: client)
|
||||||
|
} else if let newState = newState as? InputState.Inputting {
|
||||||
|
handle(state: newState, previous: previous, client: client)
|
||||||
|
} else if let newState = newState as? InputState.Marking {
|
||||||
|
handle(state: newState, previous: previous, client: client)
|
||||||
|
} else if let newState = newState as? InputState.ChoosingCandidate {
|
||||||
|
handle(state: newState, previous: previous, client: client)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func commit(text: String, client: Any!) {
|
||||||
|
|
||||||
|
func convertToSimplifiedChineseIfRequired(_ text: String) -> String {
|
||||||
|
if !Preferences.chineseConversionEnabled {
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
if Preferences.chineseConversionStyle == 1 {
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
return Preferences.chineseConversionEngine == 1 ? VXHanConvert.convertToSimplified(from: text) : OpenCCBridge.convertToSimplified(text) ?? ""
|
||||||
|
}
|
||||||
|
|
||||||
|
let buffer = convertToSimplifiedChineseIfRequired(text)
|
||||||
|
if buffer.isEmpty {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// if it's Terminal, we don't commit at the first call (the client of which will not be IPMDServerClientWrapper)
|
||||||
|
// then we defer the update in the next runloop round -- so that the composing buffer is not
|
||||||
|
// meaninglessly flushed, an annoying bug in Terminal.app since Mac OS X 10.5
|
||||||
|
if (client as? IMKTextInput)?.bundleIdentifier() == "com.apple.Terminal" && NSStringFromClass(client.self as! AnyClass) != "IPMDServerClientWrapper" {
|
||||||
|
let currentDeferredClient = currentDeferredClient
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) {
|
||||||
|
(currentDeferredClient as? IMKTextInput)?.insertText(buffer, replacementRange: NSRange(location: NSNotFound, length: NSNotFound))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(client as? IMKTextInput)?.insertText(buffer, replacementRange: NSRange(location: NSNotFound, length: NSNotFound))
|
||||||
|
}
|
||||||
|
|
||||||
|
private func handle(state: InputState.Deactivated, previous: InputState, client: Any?) {
|
||||||
|
currentDeferredClient = nil
|
||||||
|
currentCandidateClient = nil
|
||||||
|
|
||||||
|
gCurrentCandidateController?.delegate = nil
|
||||||
|
gCurrentCandidateController?.visible = false
|
||||||
|
hideTooltip()
|
||||||
|
|
||||||
|
if let previous = previous as? InputState.NotEmpty {
|
||||||
|
commit(text: previous.composingBuffer, client: client)
|
||||||
|
}
|
||||||
|
(client as? IMKTextInput)?.setMarkedText("", selectionRange: NSMakeRange(0, 0), replacementRange: NSMakeRange(NSNotFound, NSNotFound))
|
||||||
|
}
|
||||||
|
|
||||||
|
private func handle(state: InputState.Empty, previous: InputState, client: Any?) {
|
||||||
|
gCurrentCandidateController?.visible = false
|
||||||
|
hideTooltip()
|
||||||
|
|
||||||
|
guard let client = client as? IMKTextInput else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let previous = previous as? InputState.NotEmpty {
|
||||||
|
commit(text: previous.composingBuffer, client: client)
|
||||||
|
}
|
||||||
|
client.setMarkedText("", selectionRange: NSMakeRange(0, 0), replacementRange: NSMakeRange(NSNotFound, NSNotFound))
|
||||||
|
}
|
||||||
|
|
||||||
|
private func handle(state: InputState.EmptyIgnoringPreviousState, previous: InputState, client: Any!) {
|
||||||
|
gCurrentCandidateController?.visible = false
|
||||||
|
hideTooltip()
|
||||||
|
|
||||||
|
guard let client = client as? IMKTextInput else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
client.setMarkedText("", selectionRange: NSMakeRange(0, 0), replacementRange: NSMakeRange(NSNotFound, NSNotFound))
|
||||||
|
}
|
||||||
|
|
||||||
|
private func handle(state: InputState.Committing, previous: InputState, client: Any?) {
|
||||||
|
gCurrentCandidateController?.visible = false
|
||||||
|
hideTooltip()
|
||||||
|
|
||||||
|
guard let client = client as? IMKTextInput else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let poppedText = state.poppedText
|
||||||
|
if !poppedText.isEmpty {
|
||||||
|
commit(text: poppedText, client: client)
|
||||||
|
}
|
||||||
|
client.setMarkedText("", selectionRange: NSMakeRange(0, 0), replacementRange: NSMakeRange(NSNotFound, NSNotFound))
|
||||||
|
}
|
||||||
|
|
||||||
|
private func handle(state: InputState.Inputting, previous: InputState, client: Any?) {
|
||||||
|
gCurrentCandidateController?.visible = false
|
||||||
|
hideTooltip()
|
||||||
|
|
||||||
|
guard let client = client as? IMKTextInput else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let poppedText = state.poppedText
|
||||||
|
if !poppedText.isEmpty {
|
||||||
|
commit(text: poppedText, client: client)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 this composing buffer
|
||||||
|
client.setMarkedText(state.attributedString, selectionRange: NSMakeRange(Int(state.cursorIndex), 0), replacementRange: NSMakeRange(NSNotFound, NSNotFound))
|
||||||
|
}
|
||||||
|
|
||||||
|
private func handle(state: InputState.Marking, previous: InputState, client: Any?) {
|
||||||
|
gCurrentCandidateController?.visible = false
|
||||||
|
guard let client = client as? IMKTextInput else {
|
||||||
|
hideTooltip()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 this composing buffer
|
||||||
|
client.setMarkedText(state.attributedString, selectionRange: NSMakeRange(Int(state.cursorIndex), 0), replacementRange: NSMakeRange(NSNotFound, NSNotFound))
|
||||||
|
|
||||||
|
if state.tooltip.isEmpty {
|
||||||
|
hideTooltip()
|
||||||
|
} else {
|
||||||
|
show(tooltip: state.tooltip, composingBuffer: state.composingBuffer, cursorIndex: state.cursorIndex, client: client)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func handle(state: InputState.ChoosingCandidate, previous: InputState, client: Any?) {
|
||||||
|
hideTooltip()
|
||||||
|
guard let client = client as? IMKTextInput else {
|
||||||
|
gCurrentCandidateController?.visible = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 this composing buffer
|
||||||
|
client.setMarkedText(state.attributedString, selectionRange: NSMakeRange(Int(state.cursorIndex), 0), replacementRange: NSMakeRange(NSNotFound, NSNotFound))
|
||||||
|
if previous is InputState.ChoosingCandidate == false {
|
||||||
|
show(candidateWindowWith: state, client: client)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: -
|
||||||
|
|
||||||
|
extension McBopomofoInputMethodController {
|
||||||
|
|
||||||
|
private func show(candidateWindowWith state: InputState.ChoosingCandidate, client: Any!) {
|
||||||
|
if state.useVerticalMode {
|
||||||
|
gCurrentCandidateController = McBopomofoInputMethodController.verticalCandidateController
|
||||||
|
} else if Preferences.useHorizontalCandidateList {
|
||||||
|
gCurrentCandidateController = McBopomofoInputMethodController.horizontalCandidateController
|
||||||
|
} else {
|
||||||
|
gCurrentCandidateController = McBopomofoInputMethodController.verticalCandidateController
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the attributes for the candidate panel (which uses NSAttributedString)
|
||||||
|
let textSize = Preferences.candidateListTextSize
|
||||||
|
let keyLabelSize = max(textSize / 2, kMinKeyLabelSize)
|
||||||
|
|
||||||
|
func fallbackFont(name: String?, size: CGFloat) -> NSFont {
|
||||||
|
if let name = name {
|
||||||
|
return NSFont(name: name, size: size) ?? NSFont.systemFont(ofSize: size)
|
||||||
|
}
|
||||||
|
return NSFont.systemFont(ofSize: size)
|
||||||
|
}
|
||||||
|
|
||||||
|
gCurrentCandidateController?.keyLabelFont = fallbackFont(name: Preferences.candidateKeyLabelFontName, size: keyLabelSize)
|
||||||
|
gCurrentCandidateController?.candidateFont = fallbackFont(name: Preferences.candidateTextFontName, size: textSize)
|
||||||
|
|
||||||
|
let candidateKeys = Preferences.candidateKeys
|
||||||
|
let keyLabels = candidateKeys.count > 4 ? Array(candidateKeys) : Array(Preferences.defaultCandidateKeys)
|
||||||
|
|
||||||
|
gCurrentCandidateController?.keyLabels = Array(keyLabels.map {
|
||||||
|
String($0)
|
||||||
|
})
|
||||||
|
gCurrentCandidateController?.delegate = self
|
||||||
|
gCurrentCandidateController?.reloadData()
|
||||||
|
currentCandidateClient = client
|
||||||
|
|
||||||
|
gCurrentCandidateController?.visible = true
|
||||||
|
|
||||||
|
var lineHeightRect = NSMakeRect(0.0, 0.0, 16.0, 16.0)
|
||||||
|
var cursor = state.cursorIndex
|
||||||
|
if cursor == state.composingBuffer.count && cursor != 0 {
|
||||||
|
cursor -= 1
|
||||||
|
}
|
||||||
|
|
||||||
|
(client as? IMKTextInput)?.attributes(forCharacterIndex: Int(cursor), lineHeightRectangle: &lineHeightRect)
|
||||||
|
|
||||||
|
if state.useVerticalMode {
|
||||||
|
gCurrentCandidateController?.set(windowTopLeftPoint: NSMakePoint(lineHeightRect.origin.x + lineHeightRect.size.width + 4.0, lineHeightRect.origin.y - 4.0), bottomOutOfScreenAdjustmentHeight: lineHeightRect.size.height + 4.0)
|
||||||
|
} else {
|
||||||
|
gCurrentCandidateController?.set(windowTopLeftPoint: NSMakePoint(lineHeightRect.origin.x, lineHeightRect.origin.y - 4.0), bottomOutOfScreenAdjustmentHeight: lineHeightRect.size.height + 4.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func show(tooltip: String, composingBuffer: String, cursorIndex: UInt, client: Any!) {
|
||||||
|
var lineHeightRect = NSMakeRect(0.0, 0.0, 16.0, 16.0)
|
||||||
|
var cursor = cursorIndex
|
||||||
|
if cursor == composingBuffer.count && cursor != 0 {
|
||||||
|
cursor -= 1
|
||||||
|
}
|
||||||
|
(client as? IMKTextInput)?.attributes(forCharacterIndex: Int(cursor), lineHeightRectangle: &lineHeightRect)
|
||||||
|
McBopomofoInputMethodController.tooltipController.show(tooltip: tooltip, at: lineHeightRect.origin)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func hideTooltip() {
|
||||||
|
McBopomofoInputMethodController.tooltipController.hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: -
|
||||||
|
|
||||||
|
extension McBopomofoInputMethodController: KeyHandlerDelegate {
|
||||||
|
func candidateController(for keyHandler: KeyHandler) -> Any {
|
||||||
|
gCurrentCandidateController ?? McBopomofoInputMethodController.verticalCandidateController
|
||||||
|
}
|
||||||
|
|
||||||
|
func keyHandler(_ keyHandler: KeyHandler, didSelectCandidateAt index: Int, candidateController controller: Any) {
|
||||||
|
if let controller = controller as? CandidateController {
|
||||||
|
self.candidateController(controller, didSelectCandidateAtIndex: UInt(index))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func keyHandler(_ keyHandler: KeyHandler, didRequestWriteUserPhraseWith state: InputState) -> Bool {
|
||||||
|
guard let state = state as? InputState.Marking else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !state.validToWrite {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
LanguageModelManager.writeUserPhrase(state.userPhrase)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: -
|
||||||
|
|
||||||
|
extension McBopomofoInputMethodController: CandidateControllerDelegate {
|
||||||
|
func candidateCountForController(_ controller: CandidateController) -> UInt {
|
||||||
|
if let state = state as? InputState.ChoosingCandidate {
|
||||||
|
return UInt(state.candidates.count)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func candidateController(_ controller: CandidateController, candidateAtIndex index: UInt) -> String {
|
||||||
|
if let state = state as? InputState.ChoosingCandidate {
|
||||||
|
return state.candidates[Int(index)]
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func candidateController(_ controller: CandidateController, didSelectCandidateAtIndex index: UInt) {
|
||||||
|
gCurrentCandidateController?.visible = false
|
||||||
|
guard let state = state as? InputState.ChoosingCandidate else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let selectedValue = state.candidates[Int(index)]
|
||||||
|
keyHandler.fixNode(withValue: selectedValue)
|
||||||
|
|
||||||
|
guard let inputting = keyHandler.buildInputtingState() as? InputState.Inputting else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if keyHandler.inputMode == kPlainBopomofoModeIdentifier {
|
||||||
|
keyHandler.clear()
|
||||||
|
handle(state: .Committing(poppedText: inputting.composingBuffer), client: currentCandidateClient)
|
||||||
|
handle(state: .Empty(), client: currentDeferredClient)
|
||||||
|
} else {
|
||||||
|
handle(state: inputting, client: currentCandidateClient)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -55,182 +55,191 @@ import Cocoa
|
||||||
/// - Choosing Candidate: The candidate window is open to let the user to choose
|
/// - Choosing Candidate: The candidate window is open to let the user to choose
|
||||||
/// one among the candidates.
|
/// one among the candidates.
|
||||||
class InputState: NSObject {
|
class InputState: NSObject {
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents that the input controller is deactivated.
|
/// Represents that the input controller is deactivated.
|
||||||
class InputStateDeactivated: InputState {
|
@objc (InputStateDeactivated)
|
||||||
override var description: String {
|
class Deactivated: InputState {
|
||||||
"<InputStateDeactivated>"
|
override var description: String {
|
||||||
}
|
"<InputStateDeactivated>"
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents that the composing buffer is empty.
|
|
||||||
class InputStateEmpty: InputState {
|
|
||||||
@objc var composingBuffer: String {
|
|
||||||
""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents that the composing buffer is empty.
|
|
||||||
class InputStateEmptyIgnoringPreviousState: InputState {
|
|
||||||
@objc var composingBuffer: String {
|
|
||||||
""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents that the input controller is committing text into client app.
|
|
||||||
class InputStateCommitting: InputState {
|
|
||||||
@objc private(set) var poppedText: String = ""
|
|
||||||
|
|
||||||
@objc convenience init(poppedText: String) {
|
|
||||||
self.init()
|
|
||||||
self.poppedText = poppedText
|
|
||||||
}
|
|
||||||
|
|
||||||
override var description: String {
|
|
||||||
"<InputStateCommitting poppedText:\(poppedText)>"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents that the composing buffer is not empty.
|
|
||||||
class InputStateNotEmpty: InputState {
|
|
||||||
@objc private(set) var composingBuffer: String = ""
|
|
||||||
@objc private(set) var cursorIndex: UInt = 0
|
|
||||||
|
|
||||||
@objc init(composingBuffer: String, cursorIndex: UInt) {
|
|
||||||
self.composingBuffer = composingBuffer
|
|
||||||
self.cursorIndex = cursorIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
override var description: String {
|
|
||||||
"<InputStateNotEmpty, composingBuffer:\(composingBuffer), cursorIndex:\(cursorIndex)>"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents that the user is inputting text.
|
|
||||||
class InputStateInputting: InputStateNotEmpty {
|
|
||||||
@objc var bpmfReading: String = ""
|
|
||||||
@objc var bpmfReadingCursorIndex: UInt8 = 0
|
|
||||||
@objc var poppedText: String = ""
|
|
||||||
|
|
||||||
@objc override init(composingBuffer: String, cursorIndex: UInt) {
|
|
||||||
super.init(composingBuffer: composingBuffer, cursorIndex: cursorIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc var attributedString: NSAttributedString {
|
|
||||||
let attributedSting = NSAttributedString(string: composingBuffer, attributes: [
|
|
||||||
.underlineStyle: NSUnderlineStyle.single.rawValue,
|
|
||||||
.markedClauseSegment: 0
|
|
||||||
])
|
|
||||||
return attributedSting
|
|
||||||
}
|
|
||||||
|
|
||||||
override var description: String {
|
|
||||||
"<InputStateInputting, composingBuffer:\(composingBuffer), cursorIndex:\(cursorIndex), poppedText:\(poppedText)>"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private let kMinMarkRangeLength = 2
|
|
||||||
private let kMaxMarkRangeLength = 6
|
|
||||||
|
|
||||||
/// Represents that the user is marking a range in the composing buffer.
|
|
||||||
class InputStateMarking: InputStateNotEmpty {
|
|
||||||
@objc private(set) var markerIndex: UInt
|
|
||||||
@objc private(set) var markedRange: NSRange
|
|
||||||
@objc var tooltip: String {
|
|
||||||
|
|
||||||
if Preferences.phraseReplacementEnabled {
|
|
||||||
return NSLocalizedString("Phrase replacement mode is on. Not suggested to add phrase in the mode.", comment: "")
|
|
||||||
}
|
}
|
||||||
if Preferences.chineseConversionStyle == 1 && Preferences.chineseConversionEnabled {
|
}
|
||||||
return NSLocalizedString("Model based Chinese conversion is on. Not suggested to add phrase in the mode.", comment: "")
|
|
||||||
|
/// Represents that the composing buffer is empty.
|
||||||
|
@objc (InputStateEmpty)
|
||||||
|
class Empty: InputState {
|
||||||
|
@objc var composingBuffer: String {
|
||||||
|
""
|
||||||
}
|
}
|
||||||
if markedRange.length == 0 {
|
}
|
||||||
return ""
|
|
||||||
|
|
||||||
|
/// Represents that the composing buffer is empty.
|
||||||
|
@objc (InputStateEmptyIgnoringPreviousState)
|
||||||
|
class EmptyIgnoringPreviousState: InputState {
|
||||||
|
@objc var composingBuffer: String {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents that the input controller is committing text into client app.
|
||||||
|
@objc (InputStateCommitting)
|
||||||
|
class Committing: InputState {
|
||||||
|
@objc private(set) var poppedText: String = ""
|
||||||
|
|
||||||
|
@objc convenience init(poppedText: String) {
|
||||||
|
self.init()
|
||||||
|
self.poppedText = poppedText
|
||||||
}
|
}
|
||||||
|
|
||||||
let text = (composingBuffer as NSString).substring(with: markedRange)
|
override var description: String {
|
||||||
if markedRange.length < kMinMarkRangeLength {
|
"<InputStateCommitting poppedText:\(poppedText)>"
|
||||||
return String(format: NSLocalizedString("You are now selecting \"%@\". You can add a phrase with two or more characters.", comment: ""), text)
|
}
|
||||||
} else if (markedRange.length > kMaxMarkRangeLength) {
|
}
|
||||||
return String(format: NSLocalizedString("You are now selecting \"%@\". A phrase cannot be longer than %d characters.", comment: ""), text, kMaxMarkRangeLength)
|
/// Represents that the composing buffer is not empty.
|
||||||
|
@objc (InputStateNotEmpty)
|
||||||
|
class NotEmpty: InputState {
|
||||||
|
@objc private(set) var composingBuffer: String = ""
|
||||||
|
@objc private(set) var cursorIndex: UInt = 0
|
||||||
|
|
||||||
|
@objc init(composingBuffer: String, cursorIndex: UInt) {
|
||||||
|
self.composingBuffer = composingBuffer
|
||||||
|
self.cursorIndex = cursorIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
override var description: String {
|
||||||
|
"<InputStateNotEmpty, composingBuffer:\(composingBuffer), cursorIndex:\(cursorIndex)>"
|
||||||
}
|
}
|
||||||
return String(format: NSLocalizedString("You are now selecting \"%@\". Press enter to add a new phrase.", comment: ""), text)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private(set) var readings: [String] = []
|
/// Represents that the user is inputting text.
|
||||||
|
@objc (InputStateInputting)
|
||||||
|
class Inputting: NotEmpty {
|
||||||
|
@objc var bpmfReading: String = ""
|
||||||
|
@objc var bpmfReadingCursorIndex: UInt8 = 0
|
||||||
|
@objc var poppedText: String = ""
|
||||||
|
|
||||||
@objc init(composingBuffer: String, cursorIndex: UInt, markerIndex: UInt, readings: [String]) {
|
@objc override init(composingBuffer: String, cursorIndex: UInt) {
|
||||||
self.markerIndex = markerIndex
|
super.init(composingBuffer: composingBuffer, cursorIndex: cursorIndex)
|
||||||
let begin = min(cursorIndex, markerIndex)
|
}
|
||||||
let end = max(cursorIndex, markerIndex)
|
|
||||||
markedRange = NSMakeRange(Int(begin), Int(end - begin))
|
@objc var attributedString: NSAttributedString {
|
||||||
self.readings = readings
|
let attributedSting = NSAttributedString(string: composingBuffer, attributes: [
|
||||||
super.init(composingBuffer: composingBuffer, cursorIndex: cursorIndex)
|
.underlineStyle: NSUnderlineStyle.single.rawValue,
|
||||||
|
.markedClauseSegment: 0
|
||||||
|
])
|
||||||
|
return attributedSting
|
||||||
|
}
|
||||||
|
|
||||||
|
override var description: String {
|
||||||
|
"<InputStateInputting, composingBuffer:\(composingBuffer), cursorIndex:\(cursorIndex), poppedText:\(poppedText)>"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc var attributedString: NSAttributedString {
|
private let kMinMarkRangeLength = 2
|
||||||
let attributedSting = NSMutableAttributedString(string: composingBuffer)
|
private let kMaxMarkRangeLength = 6
|
||||||
let end = markedRange.location + markedRange.length
|
|
||||||
|
|
||||||
attributedSting.setAttributes([
|
/// Represents that the user is marking a range in the composing buffer.
|
||||||
.underlineStyle: NSUnderlineStyle.single.rawValue,
|
@objc (InputStateMarking)
|
||||||
.markedClauseSegment: 0
|
class Marking: NotEmpty {
|
||||||
], range: NSRange(location: 0, length: markedRange.location))
|
@objc private(set) var markerIndex: UInt
|
||||||
attributedSting.setAttributes([
|
@objc private(set) var markedRange: NSRange
|
||||||
.underlineStyle: NSUnderlineStyle.thick.rawValue,
|
@objc var tooltip: String {
|
||||||
.markedClauseSegment: 1
|
|
||||||
], range: markedRange)
|
if Preferences.phraseReplacementEnabled {
|
||||||
attributedSting.setAttributes([
|
return NSLocalizedString("Phrase replacement mode is on. Not suggested to add phrase in the mode.", comment: "")
|
||||||
.underlineStyle: NSUnderlineStyle.single.rawValue,
|
}
|
||||||
.markedClauseSegment: 2
|
if Preferences.chineseConversionStyle == 1 && Preferences.chineseConversionEnabled {
|
||||||
], range: NSRange(location: end,
|
return NSLocalizedString("Model based Chinese conversion is on. Not suggested to add phrase in the mode.", comment: "")
|
||||||
length: composingBuffer.count - end))
|
}
|
||||||
return attributedSting
|
if markedRange.length == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
let text = (composingBuffer as NSString).substring(with: markedRange)
|
||||||
|
if markedRange.length < kMinMarkRangeLength {
|
||||||
|
return String(format: NSLocalizedString("You are now selecting \"%@\". You can add a phrase with two or more characters.", comment: ""), text)
|
||||||
|
} else if (markedRange.length > kMaxMarkRangeLength) {
|
||||||
|
return String(format: NSLocalizedString("You are now selecting \"%@\". A phrase cannot be longer than %d characters.", comment: ""), text, kMaxMarkRangeLength)
|
||||||
|
}
|
||||||
|
return String(format: NSLocalizedString("You are now selecting \"%@\". Press enter to add a new phrase.", comment: ""), text)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private(set) var readings: [String] = []
|
||||||
|
|
||||||
|
@objc init(composingBuffer: String, cursorIndex: UInt, markerIndex: UInt, readings: [String]) {
|
||||||
|
self.markerIndex = markerIndex
|
||||||
|
let begin = min(cursorIndex, markerIndex)
|
||||||
|
let end = max(cursorIndex, markerIndex)
|
||||||
|
markedRange = NSMakeRange(Int(begin), Int(end - begin))
|
||||||
|
self.readings = readings
|
||||||
|
super.init(composingBuffer: composingBuffer, cursorIndex: cursorIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc var attributedString: NSAttributedString {
|
||||||
|
let attributedSting = NSMutableAttributedString(string: composingBuffer)
|
||||||
|
let end = markedRange.location + markedRange.length
|
||||||
|
|
||||||
|
attributedSting.setAttributes([
|
||||||
|
.underlineStyle: NSUnderlineStyle.single.rawValue,
|
||||||
|
.markedClauseSegment: 0
|
||||||
|
], range: NSRange(location: 0, length: markedRange.location))
|
||||||
|
attributedSting.setAttributes([
|
||||||
|
.underlineStyle: NSUnderlineStyle.thick.rawValue,
|
||||||
|
.markedClauseSegment: 1
|
||||||
|
], range: markedRange)
|
||||||
|
attributedSting.setAttributes([
|
||||||
|
.underlineStyle: NSUnderlineStyle.single.rawValue,
|
||||||
|
.markedClauseSegment: 2
|
||||||
|
], range: NSRange(location: end,
|
||||||
|
length: composingBuffer.count - end))
|
||||||
|
return attributedSting
|
||||||
|
}
|
||||||
|
|
||||||
|
override var description: String {
|
||||||
|
"<InputStateMarking, composingBuffer:\(composingBuffer), cursorIndex:\(cursorIndex), markedRange:\(markedRange), readings:\(readings)>"
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func convertToInputting() -> Inputting {
|
||||||
|
let state = Inputting(composingBuffer: composingBuffer, cursorIndex: cursorIndex)
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc var validToWrite: Bool {
|
||||||
|
markedRange.length >= kMinMarkRangeLength && markedRange.length <= kMaxMarkRangeLength
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc var userPhrase: String {
|
||||||
|
let text = (composingBuffer as NSString).substring(with: markedRange)
|
||||||
|
let end = markedRange.location + markedRange.length
|
||||||
|
let readings = readings[markedRange.location..<end]
|
||||||
|
let joined = readings.joined(separator: "-")
|
||||||
|
return "\(text) \(joined)"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override var description: String {
|
/// Represents that the user is choosing in a candidates list.
|
||||||
"<InputStateMarking, composingBuffer:\(composingBuffer), cursorIndex:\(cursorIndex), markedRange:\(markedRange), readings:\(readings)>"
|
@objc (InputStateChoosingCandidate)
|
||||||
|
class ChoosingCandidate: NotEmpty {
|
||||||
|
@objc private(set) var candidates: [String] = []
|
||||||
|
@objc private(set) var useVerticalMode: Bool = false
|
||||||
|
|
||||||
|
@objc init(composingBuffer: String, cursorIndex: UInt, candidates: [String], useVerticalMode: Bool) {
|
||||||
|
self.candidates = candidates
|
||||||
|
self.useVerticalMode = useVerticalMode
|
||||||
|
super.init(composingBuffer: composingBuffer, cursorIndex: cursorIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc var attributedString: NSAttributedString {
|
||||||
|
let attributedSting = NSAttributedString(string: composingBuffer, attributes: [
|
||||||
|
.underlineStyle: NSUnderlineStyle.single.rawValue,
|
||||||
|
.markedClauseSegment: 0
|
||||||
|
])
|
||||||
|
return attributedSting
|
||||||
|
}
|
||||||
|
|
||||||
|
override var description: String {
|
||||||
|
"<InputStateChoosingCandidate, candidates:\(candidates), useVerticalMode:\(useVerticalMode), composingBuffer:\(composingBuffer), cursorIndex:\(cursorIndex)>"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func convertToInputting() -> InputStateInputting {
|
|
||||||
let state = InputStateInputting(composingBuffer: composingBuffer, cursorIndex: cursorIndex)
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc var validToWrite: Bool {
|
|
||||||
markedRange.length >= kMinMarkRangeLength && markedRange.length <= kMaxMarkRangeLength
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc var userPhrase: String {
|
|
||||||
let text = (composingBuffer as NSString).substring(with: markedRange)
|
|
||||||
let end = markedRange.location + markedRange.length
|
|
||||||
let readings = readings[markedRange.location..<end]
|
|
||||||
let joined = readings.joined(separator: "-")
|
|
||||||
return "\(text) \(joined)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents that the user is choosing in a candidates list.
|
|
||||||
class InputStateChoosingCandidate: InputStateNotEmpty {
|
|
||||||
@objc private(set) var candidates: [String] = []
|
|
||||||
@objc private(set) var useVerticalMode: Bool = false
|
|
||||||
|
|
||||||
@objc init(composingBuffer: String, cursorIndex: UInt, candidates: [String], useVerticalMode: Bool) {
|
|
||||||
self.candidates = candidates
|
|
||||||
self.useVerticalMode = useVerticalMode
|
|
||||||
super.init(composingBuffer: composingBuffer, cursorIndex: cursorIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc var attributedString: NSAttributedString {
|
|
||||||
let attributedSting = NSAttributedString(string: composingBuffer, attributes: [
|
|
||||||
.underlineStyle: NSUnderlineStyle.single.rawValue,
|
|
||||||
.markedClauseSegment: 0
|
|
||||||
])
|
|
||||||
return attributedSting
|
|
||||||
}
|
|
||||||
|
|
||||||
override var description: String {
|
|
||||||
"<InputStateChoosingCandidate, candidates:\(candidates), useVerticalMode:\(useVerticalMode), composingBuffer:\(composingBuffer), cursorIndex:\(cursorIndex)>"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,13 +22,9 @@
|
||||||
// OTHER DEALINGS IN THE SOFTWARE.
|
// OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
#import <string>
|
|
||||||
@import CandidateUI;
|
|
||||||
|
|
||||||
@class KeyHandlerInput;
|
@class KeyHandlerInput;
|
||||||
@class InputState;
|
@class InputState;
|
||||||
@class InputStateInputting;
|
|
||||||
@class InputStateMarking;
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@ -38,9 +34,9 @@ extern NSString *const kPlainBopomofoModeIdentifier;
|
||||||
@class KeyHandler;
|
@class KeyHandler;
|
||||||
|
|
||||||
@protocol KeyHandlerDelegate <NSObject>
|
@protocol KeyHandlerDelegate <NSObject>
|
||||||
- (VTCandidateController *)candidateControllerForKeyHandler:(KeyHandler *)keyHandler;
|
- (id)candidateControllerForKeyHandler:(KeyHandler *)keyHandler;
|
||||||
- (void)keyHandler:(KeyHandler *)keyHandler didSelectCandidateAtIndex:(NSInteger)index candidateController:(VTCandidateController *)controller;
|
- (void)keyHandler:(KeyHandler *)keyHandler didSelectCandidateAtIndex:(NSInteger)index candidateController:(id)controller;
|
||||||
- (BOOL)keyHandler:(KeyHandler *)keyHandler didRequestWriteUserPhraseWithState:(InputStateMarking *)state;
|
- (BOOL)keyHandler:(KeyHandler *)keyHandler didRequestWriteUserPhraseWithState:(InputState *)state;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface KeyHandler : NSObject
|
@interface KeyHandler : NSObject
|
||||||
|
@ -52,10 +48,10 @@ candidateSelectionCallback:(void (^)(void))candidateSelectionCallback
|
||||||
errorCallback:(void (^)(void))errorCallback;
|
errorCallback:(void (^)(void))errorCallback;
|
||||||
|
|
||||||
- (void)syncWithPreferences;
|
- (void)syncWithPreferences;
|
||||||
- (void)fixNodeWithValue:(std::string)value;
|
- (void)fixNodeWithValue:(NSString *)value;
|
||||||
- (void)clear;
|
- (void)clear;
|
||||||
|
|
||||||
- (InputStateInputting *)_buildInputtingState;
|
- (InputState *)buildInputtingState;
|
||||||
|
|
||||||
@property (strong, nonatomic) NSString *inputMode;
|
@property (strong, nonatomic) NSString *inputMode;
|
||||||
@property (weak, nonatomic) id <KeyHandlerDelegate> delegate;
|
@property (weak, nonatomic) id <KeyHandlerDelegate> delegate;
|
||||||
|
|
|
@ -25,10 +25,13 @@
|
||||||
#import "Gramambular.h"
|
#import "Gramambular.h"
|
||||||
#import "McBopomofoLM.h"
|
#import "McBopomofoLM.h"
|
||||||
#import "UserOverrideModel.h"
|
#import "UserOverrideModel.h"
|
||||||
#import "LanguageModelManager.h"
|
#import "LanguageModelManager+Privates.h"
|
||||||
#import "OVUTF8Helper.h"
|
#import "OVUTF8Helper.h"
|
||||||
#import "KeyHandler.h"
|
#import "KeyHandler.h"
|
||||||
#import "McBopomofo-Swift.h"
|
#import "McBopomofo-Swift.h"
|
||||||
|
#import <string>
|
||||||
|
|
||||||
|
@import CandidateUI;
|
||||||
|
|
||||||
// C++ namespace usages
|
// C++ namespace usages
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
@ -191,12 +194,13 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot
|
||||||
_languageModel->setExternalConverterEnabled(Preferences.chineseConversionStyle == 1);
|
_languageModel->setExternalConverterEnabled(Preferences.chineseConversionStyle == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)fixNodeWithValue:(std::string)value
|
- (void)fixNodeWithValue:(NSString *)value
|
||||||
{
|
{
|
||||||
size_t cursorIndex = [self _actualCandidateCursorIndex];
|
size_t cursorIndex = [self _actualCandidateCursorIndex];
|
||||||
_builder->grid().fixNodeSelectedCandidate(cursorIndex, value);
|
string stringValue = [value UTF8String];
|
||||||
|
_builder->grid().fixNodeSelectedCandidate(cursorIndex, stringValue);
|
||||||
if (_inputMode != kPlainBopomofoModeIdentifier) {
|
if (_inputMode != kPlainBopomofoModeIdentifier) {
|
||||||
_userOverrideModel->observe(_walkedNodes, cursorIndex, value, [[NSDate date] timeIntervalSince1970]);
|
_userOverrideModel->observe(_walkedNodes, cursorIndex, stringValue, [[NSDate date] timeIntervalSince1970]);
|
||||||
}
|
}
|
||||||
[self _walk];
|
[self _walk];
|
||||||
}
|
}
|
||||||
|
@ -297,7 +301,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot
|
||||||
// update the composing buffer
|
// update the composing buffer
|
||||||
composeReading = _bpmfReadingBuffer->hasToneMarker();
|
composeReading = _bpmfReadingBuffer->hasToneMarker();
|
||||||
if (!composeReading) {
|
if (!composeReading) {
|
||||||
InputStateInputting *inputting = [self _buildInputtingState];
|
InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState];
|
||||||
stateCallback(inputting);
|
stateCallback(inputting);
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
@ -313,7 +317,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot
|
||||||
// see if we have a unigram for this
|
// see if we have a unigram for this
|
||||||
if (!_languageModel->hasUnigramsForKey(reading)) {
|
if (!_languageModel->hasUnigramsForKey(reading)) {
|
||||||
errorCallback();
|
errorCallback();
|
||||||
InputStateInputting *inputting = [self _buildInputtingState];
|
InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState];
|
||||||
stateCallback(inputting);
|
stateCallback(inputting);
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
@ -338,7 +342,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot
|
||||||
// then update the text
|
// then update the text
|
||||||
_bpmfReadingBuffer->clear();
|
_bpmfReadingBuffer->clear();
|
||||||
|
|
||||||
InputStateInputting *inputting = [self _buildInputtingState];
|
InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState];
|
||||||
inputting.poppedText = poppedText;
|
inputting.poppedText = poppedText;
|
||||||
stateCallback(inputting);
|
stateCallback(inputting);
|
||||||
|
|
||||||
|
@ -376,7 +380,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot
|
||||||
} else if (_languageModel->hasUnigramsForKey(" ")) {
|
} else if (_languageModel->hasUnigramsForKey(" ")) {
|
||||||
_builder->insertReadingAtCursor(" ");
|
_builder->insertReadingAtCursor(" ");
|
||||||
NSString *poppedText = [self _popOverflowComposingTextAndWalk];
|
NSString *poppedText = [self _popOverflowComposingTextAndWalk];
|
||||||
InputStateInputting *inputting = [self _buildInputtingState];
|
InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState];
|
||||||
inputting.poppedText = poppedText;
|
inputting.poppedText = poppedText;
|
||||||
stateCallback(inputting);
|
stateCallback(inputting);
|
||||||
}
|
}
|
||||||
|
@ -440,7 +444,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot
|
||||||
if (_bpmfReadingBuffer->isEmpty()) {
|
if (_bpmfReadingBuffer->isEmpty()) {
|
||||||
_builder->insertReadingAtCursor(string("_punctuation_list"));
|
_builder->insertReadingAtCursor(string("_punctuation_list"));
|
||||||
NSString *poppedText = [self _popOverflowComposingTextAndWalk];
|
NSString *poppedText = [self _popOverflowComposingTextAndWalk];
|
||||||
InputStateInputting *inputting = [self _buildInputtingState];
|
InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState];
|
||||||
inputting.poppedText = poppedText;
|
inputting.poppedText = poppedText;
|
||||||
stateCallback(inputting);
|
stateCallback(inputting);
|
||||||
InputStateChoosingCandidate *choosingCandidate = [self _buildCandidateState:inputting useVerticalMode:input.useVerticalMode];
|
InputStateChoosingCandidate *choosingCandidate = [self _buildCandidateState:inputting useVerticalMode:input.useVerticalMode];
|
||||||
|
@ -510,7 +514,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_bpmfReadingBuffer->clear();
|
_bpmfReadingBuffer->clear();
|
||||||
InputStateInputting *inputting = [self _buildInputtingState];
|
InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState];
|
||||||
stateCallback(inputting);
|
stateCallback(inputting);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -543,7 +547,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot
|
||||||
} else {
|
} else {
|
||||||
if (_builder->cursorIndex() > 0) {
|
if (_builder->cursorIndex() > 0) {
|
||||||
_builder->setCursorIndex(_builder->cursorIndex() - 1);
|
_builder->setCursorIndex(_builder->cursorIndex() - 1);
|
||||||
InputStateInputting *inputting = [self _buildInputtingState];
|
InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState];
|
||||||
stateCallback(inputting);
|
stateCallback(inputting);
|
||||||
} else {
|
} else {
|
||||||
errorCallback();
|
errorCallback();
|
||||||
|
@ -579,7 +583,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot
|
||||||
} else {
|
} else {
|
||||||
if (_builder->cursorIndex() < _builder->length()) {
|
if (_builder->cursorIndex() < _builder->length()) {
|
||||||
_builder->setCursorIndex(_builder->cursorIndex() + 1);
|
_builder->setCursorIndex(_builder->cursorIndex() + 1);
|
||||||
InputStateInputting *inputting = [self _buildInputtingState];
|
InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState];
|
||||||
stateCallback(inputting);
|
stateCallback(inputting);
|
||||||
} else {
|
} else {
|
||||||
errorCallback();
|
errorCallback();
|
||||||
|
@ -604,7 +608,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot
|
||||||
|
|
||||||
if (_builder->cursorIndex()) {
|
if (_builder->cursorIndex()) {
|
||||||
_builder->setCursorIndex(0);
|
_builder->setCursorIndex(0);
|
||||||
InputStateInputting *inputting = [self _buildInputtingState];
|
InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState];
|
||||||
stateCallback(inputting);
|
stateCallback(inputting);
|
||||||
} else {
|
} else {
|
||||||
errorCallback();
|
errorCallback();
|
||||||
|
@ -628,7 +632,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot
|
||||||
|
|
||||||
if (_builder->cursorIndex() != _builder->length()) {
|
if (_builder->cursorIndex() != _builder->length()) {
|
||||||
_builder->setCursorIndex(_builder->length());
|
_builder->setCursorIndex(_builder->length());
|
||||||
InputStateInputting *inputting = [self _buildInputtingState];
|
InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState];
|
||||||
stateCallback(inputting);
|
stateCallback(inputting);
|
||||||
} else {
|
} else {
|
||||||
errorCallback();
|
errorCallback();
|
||||||
|
@ -666,7 +670,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot
|
||||||
_bpmfReadingBuffer->backspace();
|
_bpmfReadingBuffer->backspace();
|
||||||
}
|
}
|
||||||
|
|
||||||
InputStateInputting *inputting = [self _buildInputtingState];
|
InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState];
|
||||||
if (!inputting.composingBuffer.length) {
|
if (!inputting.composingBuffer.length) {
|
||||||
InputStateEmptyIgnoringPreviousState *empty = [[InputStateEmptyIgnoringPreviousState alloc] init];
|
InputStateEmptyIgnoringPreviousState *empty = [[InputStateEmptyIgnoringPreviousState alloc] init];
|
||||||
stateCallback(empty);
|
stateCallback(empty);
|
||||||
|
@ -686,7 +690,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot
|
||||||
if (_builder->cursorIndex() != _builder->length()) {
|
if (_builder->cursorIndex() != _builder->length()) {
|
||||||
_builder->deleteReadingAfterCursor();
|
_builder->deleteReadingAfterCursor();
|
||||||
[self _walk];
|
[self _walk];
|
||||||
InputStateInputting *inputting = [self _buildInputtingState];
|
InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState];
|
||||||
if (!inputting.composingBuffer.length) {
|
if (!inputting.composingBuffer.length) {
|
||||||
InputStateEmptyIgnoringPreviousState *empty = [[InputStateEmptyIgnoringPreviousState alloc] init];
|
InputStateEmptyIgnoringPreviousState *empty = [[InputStateEmptyIgnoringPreviousState alloc] init];
|
||||||
stateCallback(empty);
|
stateCallback(empty);
|
||||||
|
@ -745,7 +749,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
InputStateInputting *inputting = [self _buildInputtingState];
|
InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState];
|
||||||
inputting.poppedText = poppedText;
|
inputting.poppedText = poppedText;
|
||||||
stateCallback(inputting);
|
stateCallback(inputting);
|
||||||
|
|
||||||
|
@ -775,7 +779,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot
|
||||||
UniChar charCode = input.charCode;
|
UniChar charCode = input.charCode;
|
||||||
|
|
||||||
if (charCode == 27) {
|
if (charCode == 27) {
|
||||||
InputStateInputting *inputting = [self _buildInputtingState];
|
InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState];
|
||||||
stateCallback(inputting);
|
stateCallback(inputting);
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
@ -786,7 +790,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot
|
||||||
errorCallback();
|
errorCallback();
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
InputStateInputting *inputting = [self _buildInputtingState];
|
InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState];
|
||||||
stateCallback(inputting);
|
stateCallback(inputting);
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
@ -842,7 +846,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot
|
||||||
InputStateEmptyIgnoringPreviousState *empty = [[InputStateEmptyIgnoringPreviousState alloc] init];
|
InputStateEmptyIgnoringPreviousState *empty = [[InputStateEmptyIgnoringPreviousState alloc] init];
|
||||||
stateCallback(empty);
|
stateCallback(empty);
|
||||||
} else {
|
} else {
|
||||||
InputStateInputting *inputting = [self _buildInputtingState];
|
InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState];
|
||||||
stateCallback(inputting);
|
stateCallback(inputting);
|
||||||
}
|
}
|
||||||
return YES;
|
return YES;
|
||||||
|
@ -1029,7 +1033,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot
|
||||||
|
|
||||||
#pragma mark - States Building
|
#pragma mark - States Building
|
||||||
|
|
||||||
- (InputStateInputting *)_buildInputtingState
|
- (InputStateInputting *)buildInputtingState
|
||||||
{
|
{
|
||||||
// "updating the composing buffer" means to request the client to "refresh" the text input buffer
|
// "updating the composing buffer" means to request the client to "refresh" the text input buffer
|
||||||
// with our "composing text"
|
// with our "composing text"
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
#import "LanguageModelManager.h"
|
||||||
|
#import "UserOverrideModel.h"
|
||||||
|
#import "McBopomofoLM.h"
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@interface LanguageModelManager ()
|
||||||
|
@property (class, readonly, nonatomic) McBopomofo::McBopomofoLM *languageModelMcBopomofo;
|
||||||
|
@property (class, readonly, nonatomic) McBopomofo::McBopomofoLM *languageModelPlainBopomofo;
|
||||||
|
@property (class, readonly, nonatomic) McBopomofo::UserOverrideModel *userOverrideModel;
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
|
@ -22,8 +22,6 @@
|
||||||
// OTHER DEALINGS IN THE SOFTWARE.
|
// OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
#import "UserOverrideModel.h"
|
|
||||||
#import "McBopomofoLM.h"
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@ -41,9 +39,8 @@ NS_ASSUME_NONNULL_BEGIN
|
||||||
@property (class, readonly, nonatomic) NSString *excludedPhrasesDataPathMcBopomofo;
|
@property (class, readonly, nonatomic) NSString *excludedPhrasesDataPathMcBopomofo;
|
||||||
@property (class, readonly, nonatomic) NSString *excludedPhrasesDataPathPlainBopomofo;
|
@property (class, readonly, nonatomic) NSString *excludedPhrasesDataPathPlainBopomofo;
|
||||||
@property (class, readonly, nonatomic) NSString *phraseReplacementDataPathMcBopomofo;
|
@property (class, readonly, nonatomic) NSString *phraseReplacementDataPathMcBopomofo;
|
||||||
@property (class, readonly, nonatomic) McBopomofo::McBopomofoLM *languageModelMcBopomofo;
|
@property (class, assign, nonatomic) BOOL phraseReplacementEnabled;
|
||||||
@property (class, readonly, nonatomic) McBopomofo::McBopomofoLM *languageModelPlainBopomofo;
|
|
||||||
@property (class, readonly, nonatomic) McBopomofo::UserOverrideModel *userOverrideModel;
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
NS_ASSUME_NONNULL_END
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
// OTHER DEALINGS IN THE SOFTWARE.
|
// OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
#import "LanguageModelManager.h"
|
#import "LanguageModelManager.h"
|
||||||
|
#import "LanguageModelManager+Privates.h"
|
||||||
#import "McBopomofo-Swift.h"
|
#import "McBopomofo-Swift.h"
|
||||||
|
|
||||||
@import VXHanConvert;
|
@import VXHanConvert;
|
||||||
|
@ -251,4 +252,14 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, McBopomo
|
||||||
return &gUserOverrideModel;
|
return &gUserOverrideModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
+ (BOOL)phraseReplacementEnabled
|
||||||
|
{
|
||||||
|
return gLanguageModelMcBopomofo.phraseReplacementEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (void)setPhraseReplacementEnabled:(BOOL)phraseReplacementEnabled
|
||||||
|
{
|
||||||
|
gLanguageModelMcBopomofo.setPhraseReplacementEnabled(phraseReplacementEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -4,9 +4,5 @@
|
||||||
|
|
||||||
@import Foundation;
|
@import Foundation;
|
||||||
|
|
||||||
@interface LanguageModelManager : NSObject
|
#import "KeyHandler.h"
|
||||||
+ (void)loadDataModels;
|
#import "LanguageModelManager.h"
|
||||||
+ (void)loadUserPhrases;
|
|
||||||
+ (void)loadUserPhraseReplacement;
|
|
||||||
+ (void)setupDataModelValueConverter;
|
|
||||||
@end
|
|
||||||
|
|
Loading…
Reference in New Issue