Merge pull request #261 from zonble/master

Converts the input controller to Swift
This commit is contained in:
Weizhong Yang a.k.a zonble 2022-01-30 20:41:49 +08:00 committed by GitHub
commit 3099798195
21 changed files with 1418 additions and 1760 deletions

View File

@ -7,8 +7,6 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
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 */; };
@ -20,12 +18,10 @@
6A2E40F6253A69DA00D1AE1D /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6A2E40F5253A69DA00D1AE1D /* Images.xcassets */; }; 6A2E40F6253A69DA00D1AE1D /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6A2E40F5253A69DA00D1AE1D /* Images.xcassets */; };
6A2E40F9253A6AA000D1AE1D /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6A2E40F5253A69DA00D1AE1D /* Images.xcassets */; }; 6A2E40F9253A6AA000D1AE1D /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6A2E40F5253A69DA00D1AE1D /* Images.xcassets */; };
6A38BC1515FC117A00A8A51F /* data.txt in Resources */ = {isa = PBXBuildFile; fileRef = 6A38BBF615FC117A00A8A51F /* data.txt */; }; 6A38BC1515FC117A00A8A51F /* data.txt in Resources */ = {isa = PBXBuildFile; fileRef = 6A38BBF615FC117A00A8A51F /* data.txt */; };
6A38BC2815FC158A00A8A51F /* InputMethodKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6A38BC2715FC158A00A8A51F /* InputMethodKit.framework */; };
6A6ED16B2797650A0012872E /* template-phrases-replacement.txt in Resources */ = {isa = PBXBuildFile; fileRef = 6A6ED1632797650A0012872E /* template-phrases-replacement.txt */; }; 6A6ED16B2797650A0012872E /* template-phrases-replacement.txt in Resources */ = {isa = PBXBuildFile; fileRef = 6A6ED1632797650A0012872E /* template-phrases-replacement.txt */; };
6A6ED16C2797650A0012872E /* template-data.txt in Resources */ = {isa = PBXBuildFile; fileRef = 6A6ED1652797650A0012872E /* template-data.txt */; }; 6A6ED16C2797650A0012872E /* template-data.txt in Resources */ = {isa = PBXBuildFile; fileRef = 6A6ED1652797650A0012872E /* template-data.txt */; };
6A6ED16D2797650A0012872E /* template-exclude-phrases-plain-bpmf.txt in Resources */ = {isa = PBXBuildFile; fileRef = 6A6ED1672797650A0012872E /* template-exclude-phrases-plain-bpmf.txt */; }; 6A6ED16D2797650A0012872E /* template-exclude-phrases-plain-bpmf.txt in Resources */ = {isa = PBXBuildFile; fileRef = 6A6ED1672797650A0012872E /* template-exclude-phrases-plain-bpmf.txt */; };
6A6ED16E2797650A0012872E /* template-exclude-phrases.txt in Resources */ = {isa = PBXBuildFile; fileRef = 6A6ED1692797650A0012872E /* template-exclude-phrases.txt */; }; 6A6ED16E2797650A0012872E /* template-exclude-phrases.txt in Resources */ = {isa = PBXBuildFile; fileRef = 6A6ED1692797650A0012872E /* template-exclude-phrases.txt */; };
6ACA41CD15FC1D7500935EF6 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6A0D4EA615FC0D2D00ABF4B3 /* Cocoa.framework */; };
6ACA41FA15FC1D9000935EF6 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6ACA41EA15FC1D9000935EF6 /* InfoPlist.strings */; }; 6ACA41FA15FC1D9000935EF6 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6ACA41EA15FC1D9000935EF6 /* InfoPlist.strings */; };
6ACA41FB15FC1D9000935EF6 /* License.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 6ACA41EC15FC1D9000935EF6 /* License.rtf */; }; 6ACA41FB15FC1D9000935EF6 /* License.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 6ACA41EC15FC1D9000935EF6 /* License.rtf */; };
6ACA41FC15FC1D9000935EF6 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6ACA41EE15FC1D9000935EF6 /* Localizable.strings */; }; 6ACA41FC15FC1D9000935EF6 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6ACA41EE15FC1D9000935EF6 /* Localizable.strings */; };
@ -54,16 +50,14 @@
D456576E279E4F7B00DF6BC9 /* KeyHandlerInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = D456576D279E4F7B00DF6BC9 /* KeyHandlerInput.swift */; }; D456576E279E4F7B00DF6BC9 /* KeyHandlerInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = D456576D279E4F7B00DF6BC9 /* KeyHandlerInput.swift */; };
D461B792279DAC010070E734 /* InputState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D461B791279DAC010070E734 /* InputState.swift */; }; D461B792279DAC010070E734 /* InputState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D461B791279DAC010070E734 /* InputState.swift */; };
D47B92C027972AD100458394 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47B92BF27972AC800458394 /* main.swift */; }; D47B92C027972AD100458394 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47B92BF27972AC800458394 /* main.swift */; };
D47D73A427A5D43900255A50 /* KeyHandlerBopomofoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47D73A327A5D43900255A50 /* KeyHandlerBopomofoTests.swift */; };
D47F7DCE278BFB57002F9DD7 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DCD278BFB57002F9DD7 /* PreferencesWindowController.swift */; }; D47F7DCE278BFB57002F9DD7 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DCD278BFB57002F9DD7 /* PreferencesWindowController.swift */; };
D47F7DD0278C0897002F9DD7 /* NonModalAlertWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DCF278C0897002F9DD7 /* NonModalAlertWindowController.swift */; }; D47F7DD0278C0897002F9DD7 /* NonModalAlertWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D47F7DCF278C0897002F9DD7 /* NonModalAlertWindowController.swift */; };
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 */; };
D4E569E027A4123200AC2CEF /* KeyHandlerInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = D456576D279E4F7B00DF6BC9 /* KeyHandlerInput.swift */; };
D4E569E127A4128300AC2CEF /* InputState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D461B791279DAC010070E734 /* InputState.swift */; };
D4E569E227A412E700AC2CEF /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = D44FB74427915555003C80A6 /* Preferences.swift */; };
D4E569E427A414CB00AC2CEF /* data-plain-bpmf.txt in Resources */ = {isa = PBXBuildFile; fileRef = 6AD7CBC715FE555000691B5B /* data-plain-bpmf.txt */; }; D4E569E427A414CB00AC2CEF /* data-plain-bpmf.txt in Resources */ = {isa = PBXBuildFile; fileRef = 6AD7CBC715FE555000691B5B /* data-plain-bpmf.txt */; };
D4E569E527A414CB00AC2CEF /* data.txt in Resources */ = {isa = PBXBuildFile; fileRef = 6A38BBF615FC117A00A8A51F /* data.txt */; }; D4E569E527A414CB00AC2CEF /* data.txt in Resources */ = {isa = PBXBuildFile; fileRef = 6A38BBF615FC117A00A8A51F /* data.txt */; };
D4F0BBDF279AF1AF0071253C /* ArchiveUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4F0BBDE279AF1AF0071253C /* ArchiveUtil.swift */; }; D4F0BBDF279AF1AF0071253C /* ArchiveUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4F0BBDE279AF1AF0071253C /* ArchiveUtil.swift */; };
@ -98,11 +92,6 @@
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
6A0D4EA215FC0D2D00ABF4B3 /* McBopomofo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = McBopomofo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 6A0D4EA215FC0D2D00ABF4B3 /* McBopomofo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = McBopomofo.app; sourceTree = BUILT_PRODUCTS_DIR; };
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; };
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>"; };
@ -164,7 +153,6 @@
6A225A1E23679F2600F685C6 /* NotarizedArchives */ = {isa = PBXFileReference; lastKnownFileType = folder; path = NotarizedArchives; sourceTree = "<group>"; }; 6A225A1E23679F2600F685C6 /* NotarizedArchives */ = {isa = PBXFileReference; lastKnownFileType = folder; path = NotarizedArchives; sourceTree = "<group>"; };
6A2E40F5253A69DA00D1AE1D /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; }; 6A2E40F5253A69DA00D1AE1D /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
6A38BBF615FC117A00A8A51F /* data.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = data.txt; sourceTree = "<group>"; }; 6A38BBF615FC117A00A8A51F /* data.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = data.txt; sourceTree = "<group>"; };
6A38BC2715FC158A00A8A51F /* InputMethodKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = InputMethodKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.8.sdk/System/Library/Frameworks/InputMethodKit.framework; sourceTree = DEVELOPER_DIR; };
6A6ED1642797650A0012872E /* Base */ = {isa = PBXFileReference; lastKnownFileType = text; name = Base; path = "Base.lproj/template-phrases-replacement.txt"; sourceTree = "<group>"; }; 6A6ED1642797650A0012872E /* Base */ = {isa = PBXFileReference; lastKnownFileType = text; name = Base; path = "Base.lproj/template-phrases-replacement.txt"; sourceTree = "<group>"; };
6A6ED1662797650A0012872E /* Base */ = {isa = PBXFileReference; lastKnownFileType = text; name = Base; path = "Base.lproj/template-data.txt"; sourceTree = "<group>"; }; 6A6ED1662797650A0012872E /* Base */ = {isa = PBXFileReference; lastKnownFileType = text; name = Base; path = "Base.lproj/template-data.txt"; sourceTree = "<group>"; };
6A6ED1682797650A0012872E /* Base */ = {isa = PBXFileReference; lastKnownFileType = text; name = Base; path = "Base.lproj/template-exclude-phrases-plain-bpmf.txt"; sourceTree = "<group>"; }; 6A6ED1682797650A0012872E /* Base */ = {isa = PBXFileReference; lastKnownFileType = text; name = Base; path = "Base.lproj/template-exclude-phrases-plain-bpmf.txt"; sourceTree = "<group>"; };
@ -214,6 +202,7 @@
D456576D279E4F7B00DF6BC9 /* KeyHandlerInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyHandlerInput.swift; sourceTree = "<group>"; }; D456576D279E4F7B00DF6BC9 /* KeyHandlerInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyHandlerInput.swift; sourceTree = "<group>"; };
D461B791279DAC010070E734 /* InputState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputState.swift; sourceTree = "<group>"; }; D461B791279DAC010070E734 /* InputState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputState.swift; sourceTree = "<group>"; };
D47B92BF27972AC800458394 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; }; D47B92BF27972AC800458394 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
D47D73A327A5D43900255A50 /* KeyHandlerBopomofoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyHandlerBopomofoTests.swift; sourceTree = "<group>"; };
D47F7DCD278BFB57002F9DD7 /* PreferencesWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = "<group>"; }; D47F7DCD278BFB57002F9DD7 /* PreferencesWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = "<group>"; };
D47F7DCF278C0897002F9DD7 /* NonModalAlertWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonModalAlertWindowController.swift; sourceTree = "<group>"; }; D47F7DCF278C0897002F9DD7 /* NonModalAlertWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonModalAlertWindowController.swift; sourceTree = "<group>"; };
D47F7DD1278C1263002F9DD7 /* UserOverrideModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UserOverrideModel.h; sourceTree = "<group>"; }; D47F7DD1278C1263002F9DD7 /* UserOverrideModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UserOverrideModel.h; sourceTree = "<group>"; };
@ -221,10 +210,10 @@
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>"; };
D4E569DE27A40F1400AC2CEF /* KeyHandlerBopomofoTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = KeyHandlerBopomofoTests.mm; sourceTree = "<group>"; };
D4F0BBDE279AF1AF0071253C /* ArchiveUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArchiveUtil.swift; sourceTree = "<group>"; }; D4F0BBDE279AF1AF0071253C /* ArchiveUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArchiveUtil.swift; sourceTree = "<group>"; };
D4F0BBE0279AF8B30071253C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; D4F0BBE0279AF8B30071253C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
D4F0BBE2279B08900071253C /* BundleTranslocate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BundleTranslocate.h; sourceTree = "<group>"; }; D4F0BBE2279B08900071253C /* BundleTranslocate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BundleTranslocate.h; sourceTree = "<group>"; };
@ -242,11 +231,9 @@
D427F7B4279086DC004A2160 /* InputSourceHelper in Frameworks */, D427F7B4279086DC004A2160 /* InputSourceHelper in Frameworks */,
D427F7C127908EFC004A2160 /* OpenCCBridge in Frameworks */, D427F7C127908EFC004A2160 /* OpenCCBridge in Frameworks */,
D44FB74A2791B829003C80A6 /* VXHanConvert in Frameworks */, D44FB74A2791B829003C80A6 /* VXHanConvert in Frameworks */,
6A38BC2815FC158A00A8A51F /* InputMethodKit.framework in Frameworks */,
D427F7A927905E90004A2160 /* TooltipUI in Frameworks */, D427F7A927905E90004A2160 /* TooltipUI in Frameworks */,
D427F76A278C9E29004A2160 /* CandidateUI in Frameworks */, D427F76A278C9E29004A2160 /* CandidateUI in Frameworks */,
D427F7AE27907B8A004A2160 /* NotifierUI in Frameworks */, D427F7AE27907B8A004A2160 /* NotifierUI in Frameworks */,
6A0D4EA715FC0D2D00ABF4B3 /* Cocoa.framework in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -255,7 +242,6 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
D427F7B6279086F6004A2160 /* InputSourceHelper in Frameworks */, D427F7B6279086F6004A2160 /* InputSourceHelper in Frameworks */,
6ACA41CD15FC1D7500935EF6 /* Cocoa.framework in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -275,7 +261,6 @@
D427F766278C9CBD004A2160 /* Packages */, D427F766278C9CBD004A2160 /* Packages */,
6A0D4EC215FC0D3C00ABF4B3 /* Source */, 6A0D4EC215FC0D3C00ABF4B3 /* Source */,
D485D3B72796A8A000657FF3 /* McBopomofoTests */, D485D3B72796A8A000657FF3 /* McBopomofoTests */,
6A0D4EA515FC0D2D00ABF4B3 /* Frameworks */,
6A0D4EA315FC0D2D00ABF4B3 /* Products */, 6A0D4EA315FC0D2D00ABF4B3 /* Products */,
); );
sourceTree = "<group>"; sourceTree = "<group>";
@ -290,17 +275,6 @@
name = Products; name = Products;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
6A0D4EA515FC0D2D00ABF4B3 /* Frameworks */ = {
isa = PBXGroup;
children = (
6A0D4EA915FC0D2D00ABF4B3 /* AppKit.framework */,
6A0D4EA615FC0D2D00ABF4B3 /* Cocoa.framework */,
6A0D4EAB15FC0D2D00ABF4B3 /* Foundation.framework */,
6A38BC2715FC158A00A8A51F /* InputMethodKit.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
6A0D4EC215FC0D3C00ABF4B3 /* Source */ = { 6A0D4EC215FC0D3C00ABF4B3 /* Source */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -308,9 +282,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 */,
@ -500,10 +474,9 @@
D485D3B72796A8A000657FF3 /* McBopomofoTests */ = { D485D3B72796A8A000657FF3 /* McBopomofoTests */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
D47D73A327A5D43900255A50 /* KeyHandlerBopomofoTests.swift */,
D485D3B82796A8A000657FF3 /* PreferencesTests.swift */, D485D3B82796A8A000657FF3 /* PreferencesTests.swift */,
D485D3BF2796CE3200657FF3 /* VersionUpdateTests.swift */, D485D3BF2796CE3200657FF3 /* VersionUpdateTests.swift */,
D4E569DE27A40F1400AC2CEF /* KeyHandlerBopomofoTests.mm */,
D4E569DD27A40F1300AC2CEF /* McBopomofoTests-Bridging-Header.h */,
); );
path = McBopomofoTests; path = McBopomofoTests;
sourceTree = "<group>"; sourceTree = "<group>";
@ -720,12 +693,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 */,
@ -750,12 +723,9 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
D4E569DF27A40F1400AC2CEF /* KeyHandlerBopomofoTests.mm in Sources */, D47D73A427A5D43900255A50 /* KeyHandlerBopomofoTests.swift in Sources */,
D485D3B92796A8A000657FF3 /* PreferencesTests.swift in Sources */, D485D3B92796A8A000657FF3 /* PreferencesTests.swift in Sources */,
D4E569E227A412E700AC2CEF /* Preferences.swift in Sources */,
D4E569E127A4128300AC2CEF /* InputState.swift in Sources */,
D485D3C02796CE3200657FF3 /* VersionUpdateTests.swift in Sources */, D485D3C02796CE3200657FF3 /* VersionUpdateTests.swift in Sources */,
D4E569E027A4123200AC2CEF /* KeyHandlerInput.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -1288,7 +1258,6 @@
SDKROOT = macosx; SDKROOT = macosx;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_OBJC_BRIDGING_HEADER = "McBopomofoTests/McBopomofoTests-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/McBopomofo.app/Contents/MacOS/McBopomofo"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/McBopomofo.app/Contents/MacOS/McBopomofo";
@ -1333,7 +1302,6 @@
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx; SDKROOT = macosx;
SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_OBJC_BRIDGING_HEADER = "McBopomofoTests/McBopomofoTests-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/McBopomofo.app/Contents/MacOS/McBopomofo"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/McBopomofo.app/Contents/MacOS/McBopomofo";

View File

@ -1,655 +0,0 @@
#import <XCTest/XCTest.h>
#import "KeyHandler.h"
#import "LanguageModelManager.h"
#import "McBopomofoTests-Swift.h"
@interface KeyHandlerBopomofoTests : XCTestCase
@end
@implementation KeyHandlerBopomofoTests
- (void)setUp
{
[LanguageModelManager loadDataModels];
}
- (void)tearDown
{
}
- (void)testPunctuationComma
{
KeyHandler *handler = [[KeyHandler alloc] init];
handler.inputMode = kBopomofoModeIdentifier;
KeyHandlerInput *input;
__block InputState *state;
state = [[InputStateEmpty alloc] init];
input = [[KeyHandlerInput alloc] initWithInputText:@"<" keyCode:0 charCode:'<' flags:NSEventModifierFlagShift isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
XCTAssertTrue([state isKindOfClass:NSClassFromString(@"McBopomofo.InputStateInputting")], @"It should be an inputting state %@.", NSStringFromClass([state class]));
NSString *composingBuffer = [(InputStateInputting *)state composingBuffer];
XCTAssertTrue([composingBuffer isEqualToString:@""], @"It should be but %@", composingBuffer);
}
- (void)testPunctuationPeriod
{
KeyHandler *handler = [[KeyHandler alloc] init];
handler.inputMode = kBopomofoModeIdentifier;
KeyHandlerInput *input;
__block InputState *state;
state = [[InputStateEmpty alloc] init];
input = [[KeyHandlerInput alloc] initWithInputText:@">" keyCode:0 charCode:'>' flags:NSEventModifierFlagShift isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
XCTAssertTrue([state isKindOfClass:NSClassFromString(@"McBopomofo.InputStateInputting")], @"It should be an inputting state %@.", NSStringFromClass([state class]));
NSString *composingBuffer = [(InputStateInputting *)state composingBuffer];
XCTAssertTrue([composingBuffer isEqualToString:@"。"], @"It should be 。 but %@", composingBuffer);
}
- (void)testInputtingNihao
{
KeyHandler *handler = [[KeyHandler alloc] init];
handler.inputMode = kBopomofoModeIdentifier;
KeyHandlerInput *input;
__block InputState *state;
state = [[InputStateEmpty alloc] init];
input = [[KeyHandlerInput alloc] initWithInputText:@"s" keyCode:0 charCode:'s' flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
input = [[KeyHandlerInput alloc] initWithInputText:@"u" keyCode:0 charCode:'u' flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
input = [[KeyHandlerInput alloc] initWithInputText:@"3" keyCode:0 charCode:'3' flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
input = [[KeyHandlerInput alloc] initWithInputText:@"c" keyCode:0 charCode:'c' flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
input = [[KeyHandlerInput alloc] initWithInputText:@"l" keyCode:0 charCode:'l' flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
input = [[KeyHandlerInput alloc] initWithInputText:@"3" keyCode:0 charCode:'3' flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
XCTAssertTrue([state isKindOfClass:NSClassFromString(@"McBopomofo.InputStateInputting")], @"It should be an inputting state %@.", NSStringFromClass([state class]));
NSString *composingBuffer = [(InputStateInputting *)state composingBuffer];
XCTAssertTrue([composingBuffer isEqualToString:@"你好"], @"It should be 你好 but %@", composingBuffer);
}
- (void)testCommittingNihao
{
KeyHandler *handler = [[KeyHandler alloc] init];
handler.inputMode = kBopomofoModeIdentifier;
KeyHandlerInput *input;
__block InputState *state;
state = [[InputStateEmpty alloc] init];
input = [[KeyHandlerInput alloc] initWithInputText:@"s" keyCode:0 charCode:'s' flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
input = [[KeyHandlerInput alloc] initWithInputText:@"u" keyCode:0 charCode:'u' flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
input = [[KeyHandlerInput alloc] initWithInputText:@"3" keyCode:0 charCode:'3' flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
input = [[KeyHandlerInput alloc] initWithInputText:@"c" keyCode:0 charCode:'c' flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
input = [[KeyHandlerInput alloc] initWithInputText:@"l" keyCode:0 charCode:'l' flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
input = [[KeyHandlerInput alloc] initWithInputText:@"3" keyCode:0 charCode:'3' flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
__block NSInteger count = 0;
__block InputState *empty;
input = [[KeyHandlerInput alloc] initWithInputText:@" " keyCode:0 charCode:13 flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
if (!count) {
state = inState;
}
empty = inState;
count++;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
XCTAssertTrue([state isKindOfClass:NSClassFromString(@"McBopomofo.InputStateCommitting")], @"It should be a committing state %@.", NSStringFromClass([state class]));
NSString *poppedText = [(InputStateCommitting *)state poppedText];
XCTAssertTrue([poppedText isEqualToString:@"你好"], @"It should be 你好 but %@", poppedText);
XCTAssertTrue([empty isKindOfClass:NSClassFromString(@"McBopomofo.InputStateEmpty")], @"It should be an empty state %@.", NSStringFromClass([state class]));
}
- (void)testDelete
{
KeyHandler *handler = [[KeyHandler alloc] init];
handler.inputMode = kBopomofoModeIdentifier;
KeyHandlerInput *input;
__block InputState *state;
state = [[InputStateEmpty alloc] init];
input = [[KeyHandlerInput alloc] initWithInputText:@"s" keyCode:0 charCode:'s' flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
input = [[KeyHandlerInput alloc] initWithInputText:@"u" keyCode:0 charCode:'u' flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
input = [[KeyHandlerInput alloc] initWithInputText:@"3" keyCode:0 charCode:'3' flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
input = [[KeyHandlerInput alloc] initWithInputText:@"c" keyCode:0 charCode:'c' flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
input = [[KeyHandlerInput alloc] initWithInputText:@"l" keyCode:0 charCode:'l' flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
input = [[KeyHandlerInput alloc] initWithInputText:@"3" keyCode:0 charCode:'3' flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
XCTAssertTrue([state isKindOfClass:NSClassFromString(@"McBopomofo.InputStateInputting")], @"It should be an inputting state %@.", NSStringFromClass([state class]));
NSString *composingBuffer = [(InputStateInputting *)state composingBuffer];
XCTAssertTrue([composingBuffer isEqualToString:@"你好"], @"It should be 你好 but %@", composingBuffer);
XCTAssertEqual([(InputStateInputting *)state cursorIndex], 2);
input = [[KeyHandlerInput alloc] initWithInputText:@" " keyCode:123 charCode:0 flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = (InputStateInputting *)inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
XCTAssertTrue([state isKindOfClass:NSClassFromString(@"McBopomofo.InputStateInputting")], @"It should be a inputting state %@.", NSStringFromClass([state class]));
composingBuffer = [(InputStateInputting *)state composingBuffer];
XCTAssertTrue([composingBuffer isEqualToString:@"你好"], @"It should be 你好 but %@", composingBuffer);
XCTAssertEqual([(InputStateInputting *)state cursorIndex], 1);
input = [[KeyHandlerInput alloc] initWithInputText:@" " keyCode:117 charCode:0 flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = (InputStateInputting *)inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
XCTAssertTrue([state isKindOfClass:NSClassFromString(@"McBopomofo.InputStateInputting")], @"It should be a inputting state %@.", NSStringFromClass([state class]));
composingBuffer = [(InputStateInputting *)state composingBuffer];
XCTAssertTrue([composingBuffer isEqualToString:@"你"], @"It should be 你 but %@", composingBuffer);
XCTAssertEqual([(InputStateInputting *)state cursorIndex], 1);
__block BOOL errorCalled = NO;
input = [[KeyHandlerInput alloc] initWithInputText:@" " keyCode:117 charCode:0 flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = (InputStateInputting *)inState;
} candidateSelectionCallback:^{
} errorCallback:^{
errorCalled = YES;
}];
XCTAssertTrue([state isKindOfClass:NSClassFromString(@"McBopomofo.InputStateInputting")], @"It should be a inputting state %@.", NSStringFromClass([state class]));
composingBuffer = [(InputStateInputting *)state composingBuffer];
XCTAssertTrue([composingBuffer isEqualToString:@"你"], @"It should be 你 but %@", composingBuffer);
XCTAssertEqual([(InputStateInputting *)state cursorIndex], 1);
XCTAssertTrue(errorCalled);
input = [[KeyHandlerInput alloc] initWithInputText:@" " keyCode:123 charCode:0 flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = (InputStateInputting *)inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
XCTAssertTrue([state isKindOfClass:NSClassFromString(@"McBopomofo.InputStateInputting")], @"It should be a inputting state %@.", NSStringFromClass([state class]));
composingBuffer = [(InputStateInputting *)state composingBuffer];
XCTAssertTrue([composingBuffer isEqualToString:@"你"], @"It should be 你 but %@", composingBuffer);
XCTAssertEqual([(InputStateInputting *)state cursorIndex], 0);
errorCalled = NO;
input = [[KeyHandlerInput alloc] initWithInputText:@" " keyCode:117 charCode:0 flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = (InputStateInputting *)inState;
} candidateSelectionCallback:^{
} errorCallback:^{
errorCalled = YES;
}];
XCTAssertTrue([state isKindOfClass:NSClassFromString(@"McBopomofo.InputStateEmptyIgnoringPreviousState")], @"It should be a inputting state %@.", NSStringFromClass([state class]));
XCTAssertFalse(errorCalled);
}
- (void)testBackspace
{
KeyHandler *handler = [[KeyHandler alloc] init];
handler.inputMode = kBopomofoModeIdentifier;
KeyHandlerInput *input;
__block InputState *state;
state = [[InputStateEmpty alloc] init];
input = [[KeyHandlerInput alloc] initWithInputText:@"s" keyCode:0 charCode:'s' flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
input = [[KeyHandlerInput alloc] initWithInputText:@"u" keyCode:0 charCode:'u' flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
input = [[KeyHandlerInput alloc] initWithInputText:@"3" keyCode:0 charCode:'3' flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
input = [[KeyHandlerInput alloc] initWithInputText:@"c" keyCode:0 charCode:'c' flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
input = [[KeyHandlerInput alloc] initWithInputText:@"l" keyCode:0 charCode:'l' flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
input = [[KeyHandlerInput alloc] initWithInputText:@"3" keyCode:0 charCode:'3' flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
XCTAssertTrue([state isKindOfClass:NSClassFromString(@"McBopomofo.InputStateInputting")], @"It should be an inputting state %@.", NSStringFromClass([state class]));
NSString *composingBuffer = [(InputStateInputting *)state composingBuffer];
XCTAssertTrue([composingBuffer isEqualToString:@"你好"], @"It should be 你好 but %@", composingBuffer);
input = [[KeyHandlerInput alloc] initWithInputText:@" " keyCode:0 charCode:8 flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = (InputStateInputting *)inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
XCTAssertTrue([state isKindOfClass:NSClassFromString(@"McBopomofo.InputStateInputting")], @"It should be a inputting state %@.", NSStringFromClass([state class]));
composingBuffer = [(InputStateInputting *)state composingBuffer];
XCTAssertTrue([composingBuffer isEqualToString:@"你"], @"It should be 你 but %@", composingBuffer);
__block InputStateEmpty *empty;
input = [[KeyHandlerInput alloc] initWithInputText:@" " keyCode:0 charCode:8 flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
empty = (InputStateEmpty *)inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
XCTAssertTrue([empty isKindOfClass:NSClassFromString(@"McBopomofo.InputStateEmptyIgnoringPreviousState")], @"It should be a inputting state %@.", NSStringFromClass([state class]));
}
- (void)testCursor
{
KeyHandler *handler = [[KeyHandler alloc] init];
handler.inputMode = kBopomofoModeIdentifier;
KeyHandlerInput *input;
__block InputState *state;
state = [[InputStateEmpty alloc] init];
input = [[KeyHandlerInput alloc] initWithInputText:@"s" keyCode:0 charCode:'s' flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
input = [[KeyHandlerInput alloc] initWithInputText:@"u" keyCode:0 charCode:'u' flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
input = [[KeyHandlerInput alloc] initWithInputText:@"3" keyCode:0 charCode:'3' flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
input = [[KeyHandlerInput alloc] initWithInputText:@"c" keyCode:0 charCode:'c' flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
input = [[KeyHandlerInput alloc] initWithInputText:@"l" keyCode:0 charCode:'l' flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
input = [[KeyHandlerInput alloc] initWithInputText:@"3" keyCode:0 charCode:'3' flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
XCTAssertTrue([state isKindOfClass:NSClassFromString(@"McBopomofo.InputStateInputting")], @"It should be an inputting state %@.", NSStringFromClass([state class]));
NSString *composingBuffer = [(InputStateInputting *)state composingBuffer];
XCTAssertTrue([composingBuffer isEqualToString:@"你好"], @"It should be 你好 but %@", composingBuffer);
XCTAssertEqual([(InputStateInputting *)state cursorIndex], 2);
input = [[KeyHandlerInput alloc] initWithInputText:@" " keyCode:123 charCode:0 flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = (InputStateInputting *)inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
XCTAssertTrue([state isKindOfClass:NSClassFromString(@"McBopomofo.InputStateInputting")], @"It should be a inputting state %@.", NSStringFromClass([state class]));
composingBuffer = [(InputStateInputting *)state composingBuffer];
XCTAssertTrue([composingBuffer isEqualToString:@"你好"], @"It should be 你好 but %@", composingBuffer);
XCTAssertEqual([(InputStateInputting *)state cursorIndex], 1);
input = [[KeyHandlerInput alloc] initWithInputText:@" " keyCode:123 charCode:0 flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = (InputStateInputting *)inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
XCTAssertTrue([state isKindOfClass:NSClassFromString(@"McBopomofo.InputStateInputting")], @"It should be a inputting state %@.", NSStringFromClass([state class]));
composingBuffer = [(InputStateInputting *)state composingBuffer];
XCTAssertTrue([composingBuffer isEqualToString:@"你好"], @"It should be 你好 but %@", composingBuffer);
XCTAssertEqual([(InputStateInputting *)state cursorIndex], 0);
__block BOOL errorCalled = NO;
input = [[KeyHandlerInput alloc] initWithInputText:@" " keyCode:123 charCode:0 flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = (InputStateInputting *)inState;
} candidateSelectionCallback:^{
} errorCallback:^{
errorCalled = YES;
}];
XCTAssertTrue([state isKindOfClass:NSClassFromString(@"McBopomofo.InputStateInputting")], @"It should be a inputting state %@.", NSStringFromClass([state class]));
composingBuffer = [(InputStateInputting *)state composingBuffer];
XCTAssertTrue([composingBuffer isEqualToString:@"你好"], @"It should be 你好 but %@", composingBuffer);
XCTAssertEqual([(InputStateInputting *)state cursorIndex], 0);
XCTAssertTrue(errorCalled);
input = [[KeyHandlerInput alloc] initWithInputText:@" " keyCode:124 charCode:0 flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = (InputStateInputting *)inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
XCTAssertTrue([state isKindOfClass:NSClassFromString(@"McBopomofo.InputStateInputting")], @"It should be a inputting state %@.", NSStringFromClass([state class]));
composingBuffer = [(InputStateInputting *)state composingBuffer];
XCTAssertTrue([composingBuffer isEqualToString:@"你好"], @"It should be 你好 but %@", composingBuffer);
XCTAssertEqual([(InputStateInputting *)state cursorIndex], 1);
input = [[KeyHandlerInput alloc] initWithInputText:@" " keyCode:124 charCode:0 flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = (InputStateInputting *)inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
XCTAssertTrue([state isKindOfClass:NSClassFromString(@"McBopomofo.InputStateInputting")], @"It should be a inputting state %@.", NSStringFromClass([state class]));
composingBuffer = [(InputStateInputting *)state composingBuffer];
XCTAssertTrue([composingBuffer isEqualToString:@"你好"], @"It should be 你好 but %@", composingBuffer);
XCTAssertEqual([(InputStateInputting *)state cursorIndex], 2);
errorCalled = NO;
input = [[KeyHandlerInput alloc] initWithInputText:@" " keyCode:124 charCode:0 flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = (InputStateInputting *)inState;
} candidateSelectionCallback:^{
} errorCallback:^{
errorCalled = YES;
}];
XCTAssertTrue([state isKindOfClass:NSClassFromString(@"McBopomofo.InputStateInputting")], @"It should be a inputting state %@.", NSStringFromClass([state class]));
composingBuffer = [(InputStateInputting *)state composingBuffer];
XCTAssertTrue([composingBuffer isEqualToString:@"你好"], @"It should be 你好 but %@", composingBuffer);
XCTAssertEqual([(InputStateInputting *)state cursorIndex], 2);
XCTAssertTrue(errorCalled);
}
- (void)testCandidateWithDown
{
KeyHandler *handler = [[KeyHandler alloc] init];
handler.inputMode = kBopomofoModeIdentifier;
KeyHandlerInput *input;
__block InputState *state;
state = [[InputStateEmpty alloc] init];
input = [[KeyHandlerInput alloc] initWithInputText:@"s" keyCode:0 charCode:'s' flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
input = [[KeyHandlerInput alloc] initWithInputText:@"u" keyCode:0 charCode:'u' flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
input = [[KeyHandlerInput alloc] initWithInputText:@"3" keyCode:0 charCode:'3' flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
input = [[KeyHandlerInput alloc] initWithInputText:@" " keyCode:125 charCode:0 flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
XCTAssertTrue([state isKindOfClass:NSClassFromString(@"McBopomofo.InputStateChoosingCandidate")], @"It should be a inputting state %@.", NSStringFromClass([state class]));
NSArray *candidates = [(InputStateChoosingCandidate *)state candidates];
XCTAssertTrue([candidates containsObject:@"你"]);
}
- (void)testHomeAndEnd
{
KeyHandler *handler = [[KeyHandler alloc] init];
handler.inputMode = kBopomofoModeIdentifier;
KeyHandlerInput *input;
__block InputState *state;
state = [[InputStateEmpty alloc] init];
input = [[KeyHandlerInput alloc] initWithInputText:@"s" keyCode:0 charCode:'s' flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
input = [[KeyHandlerInput alloc] initWithInputText:@"u" keyCode:0 charCode:'u' flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
input = [[KeyHandlerInput alloc] initWithInputText:@"3" keyCode:0 charCode:'3' flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
input = [[KeyHandlerInput alloc] initWithInputText:@"c" keyCode:0 charCode:'c' flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
input = [[KeyHandlerInput alloc] initWithInputText:@"l" keyCode:0 charCode:'l' flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
input = [[KeyHandlerInput alloc] initWithInputText:@"3" keyCode:0 charCode:'3' flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
XCTAssertTrue([state isKindOfClass:NSClassFromString(@"McBopomofo.InputStateInputting")], @"It should be an inputting state %@.", NSStringFromClass([state class]));
NSString *composingBuffer = [(InputStateInputting *)state composingBuffer];
XCTAssertTrue([composingBuffer isEqualToString:@"你好"], @"It should be 你好 but %@", composingBuffer);
XCTAssertEqual([(InputStateInputting *)state cursorIndex], 2);
input = [[KeyHandlerInput alloc] initWithInputText:@" " keyCode:115 charCode:0 flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
XCTAssertTrue([state isKindOfClass:NSClassFromString(@"McBopomofo.InputStateInputting")], @"It should be an inputting state %@.", NSStringFromClass([state class]));
composingBuffer = [(InputStateInputting *)state composingBuffer];
XCTAssertTrue([composingBuffer isEqualToString:@"你好"], @"It should be 你好 but %@", composingBuffer);
XCTAssertEqual([(InputStateInputting *)state cursorIndex], 0);
input = [[KeyHandlerInput alloc] initWithInputText:@" " keyCode:119 charCode:0 flags:0 isVerticalMode:0];
[handler handleInput:input state:state stateCallback:^(InputState * inState) {
state = inState;
} candidateSelectionCallback:^{
} errorCallback:^{
}];
XCTAssertTrue([state isKindOfClass:NSClassFromString(@"McBopomofo.InputStateInputting")], @"It should be an inputting state %@.", NSStringFromClass([state class]));
composingBuffer = [(InputStateInputting *)state composingBuffer];
XCTAssertTrue([composingBuffer isEqualToString:@"你好"], @"It should be 你好 but %@", composingBuffer);
XCTAssertEqual([(InputStateInputting *)state cursorIndex], 2);
}
@end

View File

@ -0,0 +1,437 @@
import XCTest
@testable import McBopomofo
func charCode(_ string: String) -> UInt16 {
let scalars = string.unicodeScalars
return UInt16(scalars[scalars.startIndex].value)
}
class KeyHandlerBopomofoTests: XCTestCase {
var handler = KeyHandler()
override func setUpWithError() throws {
LanguageModelManager.loadDataModels()
handler = KeyHandler()
handler.inputMode = .bopomofo
}
override func tearDownWithError() throws {
}
func testPunctuationComma() {
let input = KeyHandlerInput(inputText: "<", keyCode: 0, charCode: charCode("<"), flags: .shift, isVerticalMode: false)
var state: InputState = InputState.Empty()
handler.handle(input, state: state) { newState in
state = newState
} candidateSelectionCallback: {
} errorCallback: {
}
XCTAssertTrue(state is InputState.Inputting, "\(state)")
if let state = state as? InputState.Inputting {
XCTAssertEqual(state.composingBuffer, "")
}
}
func testPunctuationPeriod() {
let input = KeyHandlerInput(inputText: ">", keyCode: 0, charCode: charCode(">"), flags: .shift, isVerticalMode: false)
var state: InputState = InputState.Empty()
handler.handle(input, state: state) { newState in
state = newState
} candidateSelectionCallback: {
} errorCallback: {
}
XCTAssertTrue(state is InputState.Inputting, "\(state)")
if let state = state as? InputState.Inputting {
XCTAssertEqual(state.composingBuffer, "")
}
}
func testInputting() {
var state: InputState = InputState.Empty()
let keys = Array("vul3a945j4up gj bj4z83").map { String($0) }
for key in keys {
let input = KeyHandlerInput(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalMode: false)
handler.handle(input, state: state) { newState in
state = newState
} candidateSelectionCallback: {
} errorCallback: {
}
}
XCTAssertTrue(state is InputState.Inputting, "\(state)")
if let state = state as? InputState.Inputting {
XCTAssertEqual(state.composingBuffer, "小麥注音輸入法")
}
}
func testInputtingNihao() {
var state: InputState = InputState.Empty()
let keys = Array("su3cl3").map { String($0) }
for key in keys {
let input = KeyHandlerInput(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalMode: false)
handler.handle(input, state: state) { newState in
state = newState
} candidateSelectionCallback: {
} errorCallback: {
}
}
XCTAssertTrue(state is InputState.Inputting, "\(state)")
if let state = state as? InputState.Inputting {
XCTAssertEqual(state.composingBuffer, "你好")
}
}
func testInputtingTianKong() {
var state: InputState = InputState.Empty()
let keys = Array("wu0 dj/ ").map { String($0) }
for key in keys {
let input = KeyHandlerInput(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalMode: false)
handler.handle(input, state: state) { newState in
state = newState
} candidateSelectionCallback: {
} errorCallback: {
}
}
XCTAssertTrue(state is InputState.Inputting, "\(state)")
if let state = state as? InputState.Inputting {
XCTAssertEqual(state.composingBuffer, "天空")
}
}
func testCommittingNihao() {
var state: InputState = InputState.Empty()
let keys = Array("su3cl3").map { String($0) }
for key in keys {
let input = KeyHandlerInput(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalMode: false)
handler.handle(input, state: state) { newState in
state = newState
} candidateSelectionCallback: {
} errorCallback: {
}
}
XCTAssertTrue(state is InputState.Inputting, "\(state)")
if let state = state as? InputState.Inputting {
XCTAssertEqual(state.composingBuffer, "你好")
}
let enter = KeyHandlerInput(inputText: " ", keyCode: 0, charCode: 13, flags: [], isVerticalMode: false)
var committing: InputState?
var empty: InputState?
var count = 0
handler.handle(enter, state: state) { newState in
switch count {
case 0:
committing = newState
case 1:
empty = newState
default:
break
}
count += 1
} candidateSelectionCallback: {
} errorCallback: {
}
XCTAssertTrue(committing is InputState.Committing, "\(state)")
if let committing = committing as? InputState.Committing {
XCTAssertEqual(committing.poppedText, "你好")
}
XCTAssertTrue(empty is InputState.Empty, "\(state)")
}
func testDelete() {
var state: InputState = InputState.Empty()
let keys = Array("su3cl3").map { String($0) }
for key in keys {
let input = KeyHandlerInput(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalMode: false)
handler.handle(input, state: state) { newState in
state = newState
} candidateSelectionCallback: {
} errorCallback: {
}
}
XCTAssertTrue(state is InputState.Inputting, "\(state)")
if let state = state as? InputState.Inputting {
XCTAssertEqual(state.composingBuffer, "你好")
XCTAssertEqual(state.cursorIndex, 2)
}
let left = KeyHandlerInput(inputText: " ", keyCode: KeyCode.left.rawValue, charCode: 0, flags: [], isVerticalMode: false)
let delete = KeyHandlerInput(inputText: " ", keyCode: KeyCode.delete.rawValue, charCode: 0, flags: [], isVerticalMode: false)
var errorCalled = false
handler.handle(left, state: state) { newState in
state = newState
} candidateSelectionCallback: {
} errorCallback: {
}
XCTAssertTrue(state is InputState.Inputting, "\(state)")
if let state = state as? InputState.Inputting {
XCTAssertEqual(state.composingBuffer, "你好")
XCTAssertEqual(state.cursorIndex, 1)
}
handler.handle(delete, state: state) { newState in
state = newState
} candidateSelectionCallback: {
} errorCallback: {
}
XCTAssertTrue(state is InputState.Inputting, "\(state)")
if let state = state as? InputState.Inputting {
XCTAssertEqual(state.composingBuffer, "")
XCTAssertEqual(state.cursorIndex, 1)
}
handler.handle(delete, state: state) { newState in
state = newState
} candidateSelectionCallback: {
} errorCallback: {
errorCalled = true
}
XCTAssertTrue(state is InputState.Inputting, "\(state)")
if let state = state as? InputState.Inputting {
XCTAssertEqual(state.composingBuffer, "")
XCTAssertEqual(state.cursorIndex, 1)
}
XCTAssertTrue(errorCalled)
errorCalled = false
handler.handle(left, state: state) { newState in
state = newState
} candidateSelectionCallback: {
} errorCallback: {
}
XCTAssertTrue(state is InputState.Inputting, "\(state)")
if let state = state as? InputState.Inputting {
XCTAssertEqual(state.composingBuffer, "")
XCTAssertEqual(state.cursorIndex, 0)
}
handler.handle(delete, state: state) { newState in
state = newState
} candidateSelectionCallback: {
} errorCallback: {
}
XCTAssertTrue(state is InputState.EmptyIgnoringPreviousState, "\(state)")
}
func testBackspace() {
var state: InputState = InputState.Empty()
let keys = Array("su3cl3").map { String($0) }
for key in keys {
let input = KeyHandlerInput(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalMode: false)
handler.handle(input, state: state) { newState in
state = newState
} candidateSelectionCallback: {
} errorCallback: {
}
}
XCTAssertTrue(state is InputState.Inputting, "\(state)")
if let state = state as? InputState.Inputting {
XCTAssertEqual(state.composingBuffer, "你好")
XCTAssertEqual(state.cursorIndex, 2)
}
let backspace = KeyHandlerInput(inputText: " ", keyCode: 0, charCode: 8, flags: [], isVerticalMode: false)
handler.handle(backspace, state: state) { newState in
state = newState
} candidateSelectionCallback: {
} errorCallback: {
}
XCTAssertTrue(state is InputState.Inputting, "\(state)")
if let state = state as? InputState.Inputting {
XCTAssertEqual(state.composingBuffer, "")
XCTAssertEqual(state.cursorIndex, 1)
}
handler.handle(backspace, state: state) { newState in
state = newState
} candidateSelectionCallback: {
} errorCallback: {
}
XCTAssertTrue(state is InputState.EmptyIgnoringPreviousState, "\(state)")
}
func testCursor() {
var state: InputState = InputState.Empty()
let keys = Array("su3cl3").map { String($0) }
for key in keys {
let input = KeyHandlerInput(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalMode: false)
handler.handle(input, state: state) { newState in
state = newState
} candidateSelectionCallback: {
} errorCallback: {
}
}
XCTAssertTrue(state is InputState.Inputting, "\(state)")
if let state = state as? InputState.Inputting {
XCTAssertEqual(state.composingBuffer, "你好")
XCTAssertEqual(state.cursorIndex, 2)
}
let left = KeyHandlerInput(inputText: " ", keyCode: KeyCode.left.rawValue, charCode: 0, flags: [], isVerticalMode: false)
let right = KeyHandlerInput(inputText: " ", keyCode: KeyCode.right.rawValue, charCode: 0, flags: [], isVerticalMode: false)
var errorCalled = false
handler.handle(left, state: state) { newState in
state = newState
} candidateSelectionCallback: {
} errorCallback: {
}
XCTAssertTrue(state is InputState.Inputting, "\(state)")
if let state = state as? InputState.Inputting {
XCTAssertEqual(state.composingBuffer, "你好")
XCTAssertEqual(state.cursorIndex, 1)
}
handler.handle(left, state: state) { newState in
state = newState
} candidateSelectionCallback: {
} errorCallback: {
}
XCTAssertTrue(state is InputState.Inputting, "\(state)")
if let state = state as? InputState.Inputting {
XCTAssertEqual(state.composingBuffer, "你好")
XCTAssertEqual(state.cursorIndex, 0)
}
handler.handle(left, state: state) { newState in
state = newState
} candidateSelectionCallback: {
} errorCallback: {
errorCalled = true
}
XCTAssertTrue(state is InputState.Inputting, "\(state)")
if let state = state as? InputState.Inputting {
XCTAssertEqual(state.composingBuffer, "你好")
XCTAssertEqual(state.cursorIndex, 0)
}
XCTAssertTrue(errorCalled)
handler.handle(right, state: state) { newState in
state = newState
} candidateSelectionCallback: {
} errorCallback: {
}
XCTAssertTrue(state is InputState.Inputting, "\(state)")
if let state = state as? InputState.Inputting {
XCTAssertEqual(state.composingBuffer, "你好")
XCTAssertEqual(state.cursorIndex, 1)
}
handler.handle(right, state: state) { newState in
state = newState
} candidateSelectionCallback: {
} errorCallback: {
}
XCTAssertTrue(state is InputState.Inputting, "\(state)")
if let state = state as? InputState.Inputting {
XCTAssertEqual(state.composingBuffer, "你好")
XCTAssertEqual(state.cursorIndex, 2)
}
errorCalled = false
handler.handle(right, state: state) { newState in
state = newState
} candidateSelectionCallback: {
} errorCallback: {
errorCalled = true
}
XCTAssertTrue(state is InputState.Inputting, "\(state)")
if let state = state as? InputState.Inputting {
XCTAssertEqual(state.composingBuffer, "你好")
XCTAssertEqual(state.cursorIndex, 2)
}
XCTAssertTrue(errorCalled)
}
func testCandidateWithDown() {
var state: InputState = InputState.Empty()
let keys = Array("su3").map { String($0) }
for key in keys {
let input = KeyHandlerInput(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalMode: false)
handler.handle(input, state: state) { newState in
state = newState
} candidateSelectionCallback: {
} errorCallback: {
}
}
XCTAssertTrue(state is InputState.Inputting, "\(state)")
if let state = state as? InputState.Inputting {
XCTAssertEqual(state.composingBuffer, "")
XCTAssertEqual(state.cursorIndex, 1)
}
let space = KeyHandlerInput(inputText: " ", keyCode: KeyCode.down.rawValue, charCode: 0, flags: [], isVerticalMode: false)
handler.handle(space, state: state) { newState in
state = newState
} candidateSelectionCallback: {
} errorCallback: {
}
XCTAssertTrue(state is InputState.ChoosingCandidate, "\(state)")
if let state = state as? InputState.ChoosingCandidate {
XCTAssertEqual(state.composingBuffer, "")
XCTAssertEqual(state.cursorIndex, 1)
let candidates = state.candidates
XCTAssertTrue(candidates.contains(""))
}
}
func testHomeAndEnd() {
var state: InputState = InputState.Empty()
let keys = Array("su3cl3").map { String($0) }
for key in keys {
let input = KeyHandlerInput(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalMode: false)
handler.handle(input, state: state) { newState in
state = newState
} candidateSelectionCallback: {
} errorCallback: {
}
}
XCTAssertTrue(state is InputState.Inputting, "\(state)")
if let state = state as? InputState.Inputting {
XCTAssertEqual(state.composingBuffer, "你好")
XCTAssertEqual(state.cursorIndex, 2)
}
let home = KeyHandlerInput(inputText: " ", keyCode: KeyCode.home.rawValue, charCode: 0, flags: [], isVerticalMode: false)
let end = KeyHandlerInput(inputText: " ", keyCode: KeyCode.end.rawValue, charCode: 0, flags: [], isVerticalMode: false)
handler.handle(home, state: state) { newState in
state = newState
} candidateSelectionCallback: {
} errorCallback: {
}
XCTAssertTrue(state is InputState.Inputting, "\(state)")
if let state = state as? InputState.Inputting {
XCTAssertEqual(state.composingBuffer, "你好")
XCTAssertEqual(state.cursorIndex, 0)
}
handler.handle(end, state: state) { newState in
state = newState
} candidateSelectionCallback: {
} errorCallback: {
}
if let state = state as? InputState.Inputting {
XCTAssertEqual(state.composingBuffer, "你好")
XCTAssertEqual(state.cursorIndex, 2)
}
}
}

View File

@ -1,4 +0,0 @@
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//

View File

@ -1,5 +1,5 @@
import XCTest import XCTest
//@testable import McBopomofo @testable import McBopomofo
class PreferencesTests: XCTestCase { class PreferencesTests: XCTestCase {

View File

@ -64,7 +64,8 @@ fileprivate class HorizontalCandidateView: NSView {
for index in 0..<count { for index in 0..<count {
let labelRect = (keyLabels[index] as NSString).boundingRect(with: baseSize, options: .usesLineFragmentOrigin, attributes: keyLabelAttrDict) let labelRect = (keyLabels[index] as NSString).boundingRect(with: baseSize, options: .usesLineFragmentOrigin, attributes: keyLabelAttrDict)
let candidateRect = (displayedCandidates[index] as NSString).boundingRect(with: baseSize, options: .usesLineFragmentOrigin, attributes: candidateAttrDict) let candidateRect = (displayedCandidates[index] as NSString).boundingRect(with: baseSize, options: .usesLineFragmentOrigin, attributes: candidateAttrDict)
let cellWidth = max(labelRect.size.width, candidateRect.size.width) + cellPadding; let cellWidth = max(keyLabelHeight * 2,
max(labelRect.size.width, candidateRect.size.width)) + cellPadding;
newWidths.append(cellWidth) newWidths.append(cellWidth)
} }
elementWidths = newWidths elementWidths = newWidths

View File

@ -153,7 +153,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, NonModalAlertWindowControlle
func applicationDidFinishLaunching(_ notification: Notification) { func applicationDidFinishLaunching(_ notification: Notification) {
LanguageModelManager.setupDataModelValueConverter() LanguageModelManager.setupDataModelValueConverter()
LanguageModelManager.loadDataModels()
LanguageModelManager.loadUserPhrases() LanguageModelManager.loadUserPhrases()
LanguageModelManager.loadUserPhraseReplacement() LanguageModelManager.loadUserPhraseReplacement()

View File

@ -39,6 +39,11 @@ McBopomofoLM::~McBopomofoLM()
m_phraseReplacement.close(); m_phraseReplacement.close();
} }
bool McBopomofoLM::isDataModelLoaded()
{
return m_languageModel.isLoaded();
}
void McBopomofoLM::loadLanguageModel(const char* languageModelDataPath) void McBopomofoLM::loadLanguageModel(const char* languageModelDataPath)
{ {
if (languageModelDataPath) { if (languageModelDataPath) {

View File

@ -37,8 +37,8 @@ using namespace Formosa::Gramambular;
/// McBopomofoLM is a facade for managing a set of models including /// McBopomofoLM is a facade for managing a set of models including
/// the input method language model, user phrases and excluded phrases. /// the input method language model, user phrases and excluded phrases.
/// ///
/// It is the primary model class that the input controller and grammer builder /// It is the primary model class that the input controller and grammar builder
/// of McBopomofo talk to. When the grammer builder starts to build a sentense /// of McBopomofo talks to. When the grammar builder starts to build a sentence
/// from a series of BPMF readings, it passes the readings to the model to see /// from a series of BPMF readings, it passes the readings to the model to see
/// if there are valid unigrams, and use returned unigrams to produce the final /// if there are valid unigrams, and use returned unigrams to produce the final
/// results. /// results.
@ -64,6 +64,8 @@ public:
/// Asks to load the primary language model a the given path. /// Asks to load the primary language model a the given path.
/// @param languageModelPath Thw path of the language model. /// @param languageModelPath Thw path of the language model.
void loadLanguageModel(const char* languageModelPath); void loadLanguageModel(const char* languageModelPath);
/// If the data model is already loaded.
bool isDataModelLoaded();
/// Asks to load the user phrases and excluded phrases at the given path. /// Asks to load the user phrases and excluded phrases at the given path.
/// @param userPhrasesPath The path of user phrases. /// @param userPhrasesPath The path of user phrases.
/// @param excludedPhrasesPath The path of excluded phrases. /// @param excludedPhrasesPath The path of excluded phrases.
@ -84,7 +86,7 @@ public:
/// Enables or disables phrase replacement. /// Enables or disables phrase replacement.
void setPhraseReplacementEnabled(bool enabled); void setPhraseReplacementEnabled(bool enabled);
/// If phrease replacement is enabled or not. /// If phrase replacement is enabled or not.
bool phraseReplacementEnabled(); bool phraseReplacementEnabled();
/// Enables or disables the external converter. /// Enables or disables the external converter.

View File

@ -32,6 +32,14 @@
McBopomofo::ParselessLM::~ParselessLM() { close(); } McBopomofo::ParselessLM::~ParselessLM() { close(); }
bool McBopomofo::ParselessLM::isLoaded()
{
if (data_) {
return true;
}
return false;
}
bool McBopomofo::ParselessLM::open(const std::string_view& path) bool McBopomofo::ParselessLM::open(const std::string_view& path)
{ {
if (data_) { if (data_) {

View File

@ -37,6 +37,7 @@ class ParselessLM : public Formosa::Gramambular::LanguageModel {
public: public:
~ParselessLM() override; ~ParselessLM() override;
bool isLoaded();
bool open(const std::string_view& path); bool open(const std::string_view& path);
void close(); void close();

View File

@ -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

View File

@ -0,0 +1,547 @@
// 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 == .bopomofo && optionKeyPressed {
let phaseReplacementItem = menu.addItem(withTitle: NSLocalizedString("Use Phrase Replacement", comment: ""), action: #selector(togglePhraseReplacement(_:)), keyEquivalent: "")
phaseReplacementItem.state = Preferences.phraseReplacementEnabled.state
}
menu.addItem(NSMenuItem.separator())
menu.addItem(withTitle: NSLocalizedString("User Phrases", comment: ""), action: nil, keyEquivalent: "")
if inputMode == .plainBopomofo {
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(_ client: 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 = InputMode(rawValue: value as? String ?? InputMode.bopomofo.rawValue)
LanguageModelManager.loadDataModel(newInputMode)
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 togglePhraseReplacement(_ 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 checkIfUserFilesExist() -> 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 !checkIfUserFilesExist() {
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 font(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 = font(name: Preferences.candidateKeyLabelFontName, size: keyLabelSize)
gCurrentCandidateController?.candidateFont = font(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 == .plainBopomofo {
keyHandler.clear()
handle(state: .Committing(poppedText: inputting.composingBuffer), client: currentCandidateClient)
handle(state: .Empty(), client: currentDeferredClient)
} else {
handle(state: inputting, client: currentCandidateClient)
}
}
}

View File

@ -55,182 +55,197 @@ 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 {
} "<InputState.Deactivated>"
}
/// 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.
if markedRange.length == 0 { @objc (InputStateEmpty)
return "" class Empty: InputState {
@objc var composingBuffer: String {
""
} }
let text = (composingBuffer as NSString).substring(with: markedRange) override var description: String {
if markedRange.length < kMinMarkRangeLength { "<InputState.Empty>"
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] = [] /// Represents that the composing buffer is empty.
@objc (InputStateEmptyIgnoringPreviousState)
@objc init(composingBuffer: String, cursorIndex: UInt, markerIndex: UInt, readings: [String]) { class EmptyIgnoringPreviousState: InputState {
self.markerIndex = markerIndex @objc var composingBuffer: String {
let begin = min(cursorIndex, markerIndex) ""
let end = max(cursorIndex, markerIndex) }
markedRange = NSMakeRange(Int(begin), Int(end - begin)) override var description: String {
self.readings = readings "<InputState.EmptyIgnoringPreviousState>"
super.init(composingBuffer: composingBuffer, cursorIndex: cursorIndex) }
} }
@objc var attributedString: NSAttributedString { /// Represents that the input controller is committing text into client app.
let attributedSting = NSMutableAttributedString(string: composingBuffer) @objc (InputStateCommitting)
let end = markedRange.location + markedRange.length class Committing: InputState {
@objc private(set) var poppedText: String = ""
attributedSting.setAttributes([ @objc convenience init(poppedText: String) {
.underlineStyle: NSUnderlineStyle.single.rawValue, self.init()
.markedClauseSegment: 0 self.poppedText = poppedText
], range: NSRange(location: 0, length: markedRange.location)) }
attributedSting.setAttributes([
.underlineStyle: NSUnderlineStyle.thick.rawValue, override var description: String {
.markedClauseSegment: 1 "<InputState.Committing poppedText:\(poppedText)>"
], range: markedRange) }
attributedSting.setAttributes([ }
.underlineStyle: NSUnderlineStyle.single.rawValue, /// Represents that the composing buffer is not empty.
.markedClauseSegment: 2 @objc (InputStateNotEmpty)
], range: NSRange(location: end, class NotEmpty: InputState {
length: composingBuffer.count - end)) @objc private(set) var composingBuffer: String = ""
return attributedSting @objc private(set) var cursorIndex: UInt = 0
@objc init(composingBuffer: String, cursorIndex: UInt) {
self.composingBuffer = composingBuffer
self.cursorIndex = cursorIndex
}
override var description: String {
"<InputState.NotEmpty, composingBuffer:\(composingBuffer), cursorIndex:\(cursorIndex)>"
}
} }
override var description: String { /// Represents that the user is inputting text.
"<InputStateMarking, composingBuffer:\(composingBuffer), cursorIndex:\(cursorIndex), markedRange:\(markedRange), readings:\(readings)>" @objc (InputStateInputting)
class Inputting: NotEmpty {
@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 {
"<InputState.Inputting, composingBuffer:\(composingBuffer), cursorIndex:\(cursorIndex), poppedText:\(poppedText)>"
}
} }
@objc func convertToInputting() -> InputStateInputting { private let kMinMarkRangeLength = 2
let state = InputStateInputting(composingBuffer: composingBuffer, cursorIndex: cursorIndex) private let kMaxMarkRangeLength = 6
return state
/// Represents that the user is marking a range in the composing buffer.
@objc (InputStateMarking)
class Marking: NotEmpty {
@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: "")
}
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 {
"<InputState.Marking, 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)"
}
} }
@objc var validToWrite: Bool { /// Represents that the user is choosing in a candidates list.
markedRange.length >= kMinMarkRangeLength && markedRange.length <= kMaxMarkRangeLength @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 {
"<InputState.ChoosingCandidate, candidates:\(candidates), useVerticalMode:\(useVerticalMode), composingBuffer:\(composingBuffer), cursorIndex:\(cursorIndex)>"
}
} }
@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)>"
}
} }

View File

@ -22,25 +22,22 @@
// 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
extern NSString *const kBopomofoModeIdentifier; typedef NSString *const InputMode NS_TYPED_ENUM;
extern NSString *const kPlainBopomofoModeIdentifier; extern InputMode InputModeBopomofo;
extern InputMode InputModePlainBopomofo;
@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,12 +49,12 @@ 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) InputMode inputMode;
@property (weak, nonatomic) id <KeyHandlerDelegate> delegate; @property (weak, nonatomic) id <KeyHandlerDelegate> delegate;
@end @end

View File

@ -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;
@ -37,8 +40,8 @@ using namespace Formosa::Gramambular;
using namespace McBopomofo; using namespace McBopomofo;
using namespace OpenVanilla; using namespace OpenVanilla;
NSString *const kBopomofoModeIdentifier = @"org.openvanilla.inputmethod.McBopomofo.Bopomofo"; InputMode InputModeBopomofo = @"org.openvanilla.inputmethod.McBopomofo.Bopomofo";
NSString *const kPlainBopomofoModeIdentifier = @"org.openvanilla.inputmethod.McBopomofo.PlainBopomofo"; InputMode InputModePlainBopomofo = @"org.openvanilla.inputmethod.McBopomofo.PlainBopomofo";
static const double kEpsilon = 0.000001; static const double kEpsilon = 0.000001;
@ -102,12 +105,12 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot
NSString *newInputMode; NSString *newInputMode;
McBopomofoLM *newLanguageModel; McBopomofoLM *newLanguageModel;
if ([value isKindOfClass:[NSString class]] && [value isEqual:kPlainBopomofoModeIdentifier]) { if ([value isKindOfClass:[NSString class]] && [value isEqual:InputModePlainBopomofo]) {
newInputMode = kPlainBopomofoModeIdentifier; newInputMode = InputModePlainBopomofo;
newLanguageModel = [LanguageModelManager languageModelPlainBopomofo]; newLanguageModel = [LanguageModelManager languageModelPlainBopomofo];
newLanguageModel->setPhraseReplacementEnabled(false); newLanguageModel->setPhraseReplacementEnabled(false);
} else { } else {
newInputMode = kBopomofoModeIdentifier; newInputMode = InputModeBopomofo;
newLanguageModel = [LanguageModelManager languageModelMcBopomofo]; newLanguageModel = [LanguageModelManager languageModelMcBopomofo];
newLanguageModel->setPhraseReplacementEnabled(Preferences.phraseReplacementEnabled); newLanguageModel->setPhraseReplacementEnabled(Preferences.phraseReplacementEnabled);
} }
@ -157,7 +160,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot
// each Mandarin syllable is separated by a hyphen // each Mandarin syllable is separated by a hyphen
_builder->setJoinSeparator("-"); _builder->setJoinSeparator("-");
_inputMode = kBopomofoModeIdentifier; _inputMode = InputModeBopomofo;
} }
return self; return self;
} }
@ -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];
if (_inputMode != kPlainBopomofoModeIdentifier) { _builder->grid().fixNodeSelectedCandidate(cursorIndex, stringValue);
_userOverrideModel->observe(_walkedNodes, cursorIndex, value, [[NSDate date] timeIntervalSince1970]); if (_inputMode != InputModePlainBopomofo) {
_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;
} }
@ -325,7 +329,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot
NSString *poppedText = [self _popOverflowComposingTextAndWalk]; NSString *poppedText = [self _popOverflowComposingTextAndWalk];
// get user override model suggestion // get user override model suggestion
string overrideValue = (_inputMode == kPlainBopomofoModeIdentifier) ? "" : string overrideValue = (_inputMode == InputModePlainBopomofo) ? "" :
_userOverrideModel->suggest(_walkedNodes, _builder->cursorIndex(), [[NSDate date] timeIntervalSince1970]); _userOverrideModel->suggest(_walkedNodes, _builder->cursorIndex(), [[NSDate date] timeIntervalSince1970]);
if (!overrideValue.empty()) { if (!overrideValue.empty()) {
@ -338,11 +342,11 @@ 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);
if (_inputMode == kPlainBopomofoModeIdentifier) { if (_inputMode == InputModePlainBopomofo) {
InputStateChoosingCandidate *choosingCandidates = [self _buildCandidateState:inputting useVerticalMode:input.useVerticalMode]; InputStateChoosingCandidate *choosingCandidates = [self _buildCandidateState:inputting useVerticalMode:input.useVerticalMode];
if (choosingCandidates.candidates.count == 1) { if (choosingCandidates.candidates.count == 1) {
[self clear]; [self clear];
@ -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];
@ -494,7 +498,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot
// if the option is enabled, we clear everything including the composing // if the option is enabled, we clear everything including the composing
// buffer, walked nodes and the reading. // buffer, walked nodes and the reading.
[self clear]; [self clear];
InputStateEmpty *empty = [[InputStateEmpty alloc] init]; InputStateEmptyIgnoringPreviousState *empty = [[InputStateEmptyIgnoringPreviousState alloc] init];
stateCallback(empty); stateCallback(empty);
} else { } else {
// if reading is not empty, we cancel the reading; Apple's built-in // if reading is not empty, we cancel the reading; Apple's built-in
@ -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);
@ -708,7 +712,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot
- (BOOL)_handleEnterWithState:(InputState *)state stateCallback:(void (^)(InputState *))stateCallback errorCallback:(void (^)(void))errorCallback - (BOOL)_handleEnterWithState:(InputState *)state stateCallback:(void (^)(InputState *))stateCallback errorCallback:(void (^)(void))errorCallback
{ {
if ([state isKindOfClass:[InputStateInputting class]]) { if ([state isKindOfClass:[InputStateInputting class]]) {
if (_inputMode == kPlainBopomofoModeIdentifier) { if (_inputMode == InputModePlainBopomofo) {
if (!_bpmfReadingBuffer->isEmpty()) { if (!_bpmfReadingBuffer->isEmpty()) {
errorCallback(); errorCallback();
} }
@ -745,11 +749,11 @@ 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);
if (_inputMode == kPlainBopomofoModeIdentifier && _bpmfReadingBuffer->isEmpty()) { if (_inputMode == InputModePlainBopomofo && _bpmfReadingBuffer->isEmpty()) {
InputStateChoosingCandidate *candidateState = [self _buildCandidateState:inputting useVerticalMode:useVerticalMode]; InputStateChoosingCandidate *candidateState = [self _buildCandidateState:inputting useVerticalMode:useVerticalMode];
if ([candidateState.candidates count] == 1) { if ([candidateState.candidates count] == 1) {
@ -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;
} }
@ -837,12 +841,12 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot
BOOL cancelCandidateKey = (charCode == 27) || (charCode == 8) || [input isDelete]; BOOL cancelCandidateKey = (charCode == 27) || (charCode == 8) || [input isDelete];
if (cancelCandidateKey) { if (cancelCandidateKey) {
if (_inputMode == kPlainBopomofoModeIdentifier) { if (_inputMode == InputModePlainBopomofo) {
[self clear]; [self clear];
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;
@ -993,7 +997,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot
} }
} }
if (_inputMode == kPlainBopomofoModeIdentifier) { if (_inputMode == InputModePlainBopomofo) {
string layout = [self _currentLayout]; string layout = [self _currentLayout];
string punctuationNamePrefix = Preferences.halfWidthPunctuationEnabled ? string("_half_punctuation_") : string("_punctuation_"); string punctuationNamePrefix = Preferences.halfWidthPunctuationEnabled ? string("_half_punctuation_") : string("_punctuation_");
string customPunctuation = punctuationNamePrefix + layout + string(1, (char) charCode); string customPunctuation = punctuationNamePrefix + layout + string(1, (char) charCode);
@ -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"

View File

@ -1,4 +1,4 @@
// Copyright (c) 2011 and onwards The McBopomofo Authors. // Copyright (c) 2022 and onwards The McBopomofo Authors.
// //
// Permission is hereby granted, free of charge, to any person // Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation // obtaining a copy of this software and associated documentation
@ -21,12 +21,16 @@
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE. // OTHER DEALINGS IN THE SOFTWARE.
#import <Cocoa/Cocoa.h> #import "LanguageModelManager.h"
#import <InputMethodKit/InputMethodKit.h> #import "UserOverrideModel.h"
#import "McBopomofo-Swift.h" #import "McBopomofoLM.h"
@interface McBopomofoInputMethodController : IMKInputController NS_ASSUME_NONNULL_BEGIN
- (void)handleState:(InputState *)newState client:(id)client;
@interface LanguageModelManager ()
@property (class, readonly, nonatomic) McBopomofo::McBopomofoLM *languageModelMcBopomofo;
@property (class, readonly, nonatomic) McBopomofo::McBopomofoLM *languageModelPlainBopomofo;
@property (class, readonly, nonatomic) McBopomofo::UserOverrideModel *userOverrideModel;
@end @end
NS_ASSUME_NONNULL_END

View File

@ -22,14 +22,13 @@
// OTHER DEALINGS IN THE SOFTWARE. // OTHER DEALINGS IN THE SOFTWARE.
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import "UserOverrideModel.h" #import "KeyHandler.h"
#import "McBopomofoLM.h"
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
@interface LanguageModelManager : NSObject @interface LanguageModelManager : NSObject
+ (void)loadDataModels; + (void)loadDataModel:(InputMode)mode;
+ (void)loadUserPhrases; + (void)loadUserPhrases;
+ (void)loadUserPhraseReplacement; + (void)loadUserPhraseReplacement;
+ (void)setupDataModelValueConverter; + (void)setupDataModelValueConverter;
@ -41,9 +40,13 @@ 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
/// The following methods are merely for testing.
@interface LanguageModelManager ()
+ (void)loadDataModels;
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

View File

@ -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;
@ -33,16 +34,15 @@ using namespace McBopomofo;
static const int kUserOverrideModelCapacity = 500; static const int kUserOverrideModelCapacity = 500;
static const double kObservedOverrideHalflife = 5400.0; // 1.5 hr. static const double kObservedOverrideHalflife = 5400.0; // 1.5 hr.
McBopomofoLM gLanguageModelMcBopomofo; static McBopomofoLM gLanguageModelMcBopomofo;
McBopomofoLM gLanguageModelPlainBopomofo; static McBopomofoLM gLanguageModelPlainBopomofo;
UserOverrideModel gUserOverrideModel(kUserOverrideModelCapacity, kObservedOverrideHalflife); static UserOverrideModel gUserOverrideModel(kUserOverrideModelCapacity, kObservedOverrideHalflife);
NSString *const kUserDataTemplateName = @"template-data";
NSString *const kExcludedPhrasesMcBopomofoTemplateName = @"template-exclude-phrases";
NSString *const kExcludedPhrasesPlainBopomofoTemplateName = @"template-exclude-phrases-plain-bpmf";
NSString *const kPhraseReplacementTemplateName = @"template-phrases-replacement";
NSString *const kTemplateExtension = @".txt";
static NSString *const kUserDataTemplateName = @"template-data";
static NSString *const kExcludedPhrasesMcBopomofoTemplateName = @"template-exclude-phrases";
static NSString *const kExcludedPhrasesPlainBopomofoTemplateName = @"template-exclude-phrases-plain-bpmf";
static NSString *const kPhraseReplacementTemplateName = @"template-phrases-replacement";
static NSString *const kTemplateExtension = @".txt";
@implementation LanguageModelManager @implementation LanguageModelManager
@ -55,8 +55,26 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, McBopomo
+ (void)loadDataModels + (void)loadDataModels
{ {
LTLoadLanguageModelFile(@"data", gLanguageModelMcBopomofo); if (!gLanguageModelMcBopomofo.isDataModelLoaded()) {
LTLoadLanguageModelFile(@"data-plain-bpmf", gLanguageModelPlainBopomofo); LTLoadLanguageModelFile(@"data", gLanguageModelMcBopomofo);
}
if (!gLanguageModelPlainBopomofo.isDataModelLoaded()) {
LTLoadLanguageModelFile(@"data-plain-bpmf", gLanguageModelPlainBopomofo);
}
}
+ (void)loadDataModel:(InputMode)mode
{
if ([mode isEqualToString:InputModeBopomofo]) {
if (!gLanguageModelMcBopomofo.isDataModelLoaded()) {
LTLoadLanguageModelFile(@"data", gLanguageModelMcBopomofo);
}
}
if ([mode isEqualToString:InputModePlainBopomofo]) {
if (!gLanguageModelPlainBopomofo.isDataModelLoaded()) {
LTLoadLanguageModelFile(@"data-plain-bpmf", gLanguageModelPlainBopomofo);
}
}
} }
+ (void)loadUserPhrases + (void)loadUserPhrases
@ -251,4 +269,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

View File

@ -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

View File

@ -1,19 +1,13 @@
. .
├── AppDelegate.h ├── AppDelegate.swift
├── AppDelegate.m ├── Base.lproj
├── CandidateUI │   ├── Credits.rtf
│   ├── VTCandidateController.h │   ├── MainMenu.xib
│   ├── VTCandidateController.m │   ├── preferences.xib
│   ├── VTHorizontalCandidateController.h │   ├── template-data.txt
│   ├── VTHorizontalCandidateController.m │   ├── template-exclude-phrases-plain-bpmf.txt
│   ├── VTHorizontalCandidateView.h │   ├── template-exclude-phrases.txt
│   ├── VTHorizontalCandidateView.m │   └── template-phrases-replacement.txt
│   ├── VTVerticalCandidateController.h
│   ├── VTVerticalCandidateController.m
│   ├── VTVerticalCandidateTableView.h
│   ├── VTVerticalCandidateTableView.m
│   ├── VTVerticalKeyLabelStripView.h
│   └── VTVerticalKeyLabelStripView.m
├── Data ├── Data
│   ├── BPMFBase.txt │   ├── BPMFBase.txt
│   ├── BPMFMappings.txt │   ├── BPMFMappings.txt
@ -24,19 +18,18 @@
│   ├── Symbols.txt │   ├── Symbols.txt
│   ├── bin │   ├── bin
│   │   ├── C_Version │   │   ├── C_Version
│   │   │   ├── C_count.occ.exe
│   │   │   ├── Makefile │   │   │   ├── Makefile
│   │   │   ├── count.bash │   │   │   ├── count.bash
│   │   │   └── count.occurrence.c │   │   │   └── count.occurrence.c
│   │   ├── README │   │   ├── README
│   │   ├── Sample_Prep │   │   ├── Sample_Prep
│   │   │   ├── build.bash │   │   │   ├── build.bash
│   │   │   ├── filter.bash │   │   │   └── filter.bash
│   │   │   └── filter.py
│   │   ├── bpmfmap.py │   │   ├── bpmfmap.py
│   │   ├── buildFreq.py │   │   ├── buildFreq.py
│   │   ├── cook-plain-bpmf.py │   │   ├── cook-plain-bpmf.py
│   │   ├── cook.py │   │   ├── cook.py
│   │   ├── cook_util.py
│   │   ├── count.bash │   │   ├── count.bash
│   │   ├── count.occurrence.py │   │   ├── count.occurrence.py
│   │   ├── disabled │   │   ├── disabled
@ -47,7 +40,6 @@
│   │   │   ├── cook.rb │   │   │   ├── cook.rb
│   │   │   ├── count.occurrence.pl │   │   │   ├── count.occurrence.pl
│   │   │   ├── countphrase.bash │   │   │   ├── countphrase.bash
│   │   │   ├── filter.bash
│   │   │   ├── randomShuffle.bash │   │   │   ├── randomShuffle.bash
│   │   │   ├── typocorrection.bash │   │   │   ├── typocorrection.bash
│   │   │   └── utf8length.pl │   │   │   └── utf8length.pl
@ -55,6 +47,7 @@
│   │   ├── self-score-test.py │   │   ├── self-score-test.py
│   │   └── textpool.rc │   │   └── textpool.rc
│   ├── data-plain-bpmf.txt │   ├── data-plain-bpmf.txt
│   ├── data.txt
│   ├── exclusion.txt │   ├── exclusion.txt
│   ├── heterophony1.list │   ├── heterophony1.list
│   ├── heterophony2.list │   ├── heterophony2.list
@ -64,11 +57,9 @@
│   │   ├── covered_by_others.txt │   │   ├── covered_by_others.txt
│   │   ├── covered_by_singles.txt │   │   ├── covered_by_singles.txt
│   │   └── falsecount.txt │   │   └── falsecount.txt
│   ├── phrase.occ │   └── phrase.occ
│   └── problem.txt
├── Engine ├── Engine
│   ├── FastLM.cpp │   ├── CMakeLists.txt
│   ├── FastLM.h
│   ├── Gramambular │   ├── Gramambular
│   │   ├── Bigram.h │   │   ├── Bigram.h
│   │   ├── BlockReadingBuilder.h │   │   ├── BlockReadingBuilder.h
@ -81,121 +72,137 @@
│   │   ├── Span.h │   │   ├── Span.h
│   │   ├── Unigram.h │   │   ├── Unigram.h
│   │   └── Walker.h │   │   └── Walker.h
│   ├── KeyValueBlobReader.cpp
│   ├── KeyValueBlobReader.h
│   ├── KeyValueBlobReaderTest.cpp
│   ├── Mandarin │   ├── Mandarin
│   │   ├── Mandarin.cpp │   │   ├── Mandarin.cpp
│   │   └── Mandarin.h │   │   └── Mandarin.h
│   └── OpenVanilla │   ├── McBopomofoLM.cpp
│   ├── OVAroundFilter.h │   ├── McBopomofoLM.h
│   ├── OVBase.h │   ├── OpenVanilla
│   ├── OVBenchmark.h │   │   ├── OVAroundFilter.h
│   ├── OVCINDataTable.h │   │   ├── OVBase.h
│   ├── OVCINDatabaseService.h │   │   ├── OVBenchmark.h
│   ├── OVCINToSQLiteConvertor.h │   │   ├── OVCINDataTable.h
│   ├── OVCandidateService.h │   │   ├── OVCINDatabaseService.h
│   ├── OVDatabaseService.h │   │   ├── OVCINToSQLiteConvertor.h
│   ├── OVDateTimeHelper.h │   │   ├── OVCandidateService.h
│   ├── OVEncodingService.h │   │   ├── OVDatabaseService.h
│   ├── OVEventHandlingContext.h │   │   ├── OVDateTimeHelper.h
│   ├── OVException.h │   │   ├── OVEncodingService.h
│   ├── OVFileHelper.h │   │   ├── OVEventHandlingContext.h
│   ├── OVFrameworkInfo.h │   │   ├── OVException.h
│   ├── OVInputMethod.h │   │   ├── OVFileHelper.h
│   ├── OVKey.h │   │   ├── OVFrameworkInfo.h
│   ├── OVKeyPreprocessor.h │   │   ├── OVInputMethod.h
│   ├── OVKeyValueMap.h │   │   ├── OVKey.h
│   ├── OVLoaderBase.h │   │   ├── OVKeyPreprocessor.h
│   ├── OVLoaderService.h │   │   ├── OVKeyValueMap.h
│   ├── OVLocalization.h │   │   ├── OVLoaderBase.h
│   ├── OVModule.h │   │   ├── OVLoaderService.h
│   ├── OVModulePackage.h │   │   ├── OVLocalization.h
│   ├── OVOutputFilter.h │   │   ├── OVModule.h
│   ├── OVPathInfo.h │   │   ├── OVModulePackage.h
│   ├── OVSQLiteDatabaseService.h │   │   ├── OVOutputFilter.h
│   ├── OVSQLiteWrapper.h │   │   ├── OVPathInfo.h
│   ├── OVStringHelper.h │   │   ├── OVSQLiteDatabaseService.h
│   ├── OVTextBuffer.h │   │   ├── OVSQLiteWrapper.h
│   ├── OVUTF8Helper.h │   │   ├── OVStringHelper.h
│   ├── OVWildcard.h │   │   ├── OVTextBuffer.h
│   └── OpenVanilla.h │   │   ├── OVUTF8Helper.h
├── IconMaker │   │   ├── OVWildcard.h
│   ├── AppIconRendererView.h │   │   └── OpenVanilla.h
│   ├── AppIconRendererView.m │   ├── ParselessLM.cpp
│   ├── BopomofoIconRenderView.h │   ├── ParselessLM.h
│   ├── BopomofoIconRenderView.m │   ├── ParselessLMBenchmark.cpp
│   ├── IconMaker-Info.plist │   ├── ParselessLMTest.cpp
│   ├── IconMaker-Prefix.pch │   ├── ParselessPhraseDB.cpp
│   ├── IconMaker.xcodeproj │   ├── ParselessPhraseDB.h
│   │   └── project.pbxproj │   ├── ParselessPhraseDBTest.cpp
│   ├── IconMakerAppDelegate.h │   ├── PhraseReplacementMap.cpp
│   ├── IconMakerAppDelegate.m │   ├── PhraseReplacementMap.h
│   ├── ImageZoomInView.h │   ├── PhraseReplacementMapTest.cpp
│   ├── ImageZoomInView.m │   ├── UserOverrideModel.cpp
│   ├── TISIconRendererView.h │   ├── UserOverrideModel.h
│   ├── TISIconRendererView.m │   ├── UserPhrasesLM.cpp
│   ├── en.lproj │   ├── UserPhrasesLM.h
│   │   └── MainMenu.xib │   └── UserPhrasesLMTest.cpp
│   └── main.m
├── Images ├── Images
│   ├── Bopomofo.tiff │   ├── Bopomofo.tiff
│   ├── Bopomofo@2x.tiff │   ├── Bopomofo@2x.tiff
│   ├── BopomofoTextMenu.tiff │   ├── Images.xcassets
│   ├── BopomofoTextMenu@2x.tiff │   │   ├── AlertIcon.imageset
│   ├── McBopomofo.iconset │   │   │   ├── 128X128.png
│   │   ├── icon_128x128.png │   │   │   ├── 192x192.png
│   │   ├── icon_128x128@2x.png │   │   │   ├── 64X64.png
│   │   ├── icon_16x16.png │   │   │   └── Contents.json
│   │   ├── icon_16x16@2x.png │   │   ├── AppIcon.appiconset
│   │   ├── icon_256x256.png │   │   │   ├── 1024X1024.png
│   │   ├── icon_256x256@2x.png │   │   │   ├── 128X128.png
│   │   ├── icon_32x32.png │   │   │   ├── 16X16.png
│   │   ├── icon_32x32@2x.png │   │   │   ├── 256X256.png
│   │   ├── icon_512x512.png │   │   │   ├── 32X32.png
│   │   └── icon_512x512@2x.png │   │   │   ├── 512X512.png
│   │   │   ├── 64X64.png
│   │   │   └── Contents.json
│   │   └── Contents.json
│   ├── PlainBopomofo.tiff │   ├── PlainBopomofo.tiff
│   ├── PlainBopomofo@2x.tiff │   └── PlainBopomofo@2x.tiff
│   ├── favicon.ico ├── InputMethodController.swift
│   ├── favicon.png ├── InputState.swift
│   └── favicon.tiff
├── InputMethodController.h
├── InputMethodController.mm
├── Installer ├── Installer
│   ├── AppDelegate.h │   ├── AppDelegate.swift
│   ├── AppDelegate.m │   ├── ArchiveUtil.swift
│   ├── Base.lproj
│   │   └── MainMenu.xib
│   ├── BundleTranslocate.h
│   ├── BundleTranslocate.m
│   ├── Installer-Info.plist │   ├── Installer-Info.plist
│   ├── Installer-Prefix.pch │   ├── Installer-Prefix.pch
│   ├── McBopomofoInstaller-Bridging-Header.h
│   ├── NotarizedArchives
│   │   └── README.md
│   ├── en.lproj │   ├── en.lproj
│   │   ├── InfoPlist.strings │   │   ├── InfoPlist.strings
│   │   ├── License.rtf │   │   ├── License.rtf
│   │   ├── Localizable.strings │   │   └── Localizable.strings
│   │   └── MainMenu.xib
│   ├── main.m
│   └── zh-Hant.lproj │   └── zh-Hant.lproj
│   ├── InfoPlist.strings │   ├── InfoPlist.strings
│   ├── License.rtf │   ├── License.rtf
│   ├── Localizable.strings │   ├── Localizable.strings
│   └── MainMenu.xib │   └── MainMenu.xib
├── KeyHandler.h
├── KeyHandler.mm
├── KeyHandlerInput.swift
├── LanguageModelManager+Privates.h
├── LanguageModelManager.h
├── LanguageModelManager.mm
├── McBopomofo-Bridging-Header.h
├── McBopomofo-Info.plist ├── McBopomofo-Info.plist
├── McBopomofo-Prefix.pch ├── McBopomofo-Prefix.pch
├── OVInputSourceHelper.h ├── NonModalAlertWindowController.swift
├── OVInputSourceHelper.m ├── NonModalAlertWindowController.xib
├── PreferencesWindowController.h ├── Preferences.swift
├── PreferencesWindowController.m ├── PreferencesWindowController.swift
├── README ├── README
├── Tools ├── Tools
│   ├── genRTF.py │   ├── genRTF.py
│   └── tistool.m │   └── tistool.m
├── UpdateNotificationController.h
├── UpdateNotificationController.m
├── en.lproj ├── en.lproj
│   ├── Credits.rtf
│   ├── InfoPlist.strings │   ├── InfoPlist.strings
│   ├── Localizable.strings │   └── Localizable.strings
│   ├── MainMenu.xib ├── main.swift
│   ├── UpdateNotificationController.xib
│   └── preferences.xib
├── main.m
└── zh-Hant.lproj └── zh-Hant.lproj
├── Credits.rtf
├── InfoPlist.strings ├── InfoPlist.strings
├── Localizable.strings ├── Localizable.strings
├── MainMenu.xib ├── MainMenu.xib
├── UpdateNotificationController.xib ├── preferences.xib
└── preferences.xib ├── template-data.txt
├── template-exclude-phrases-plain-bpmf.txt
├── template-exclude-phrases.txt
└── template-phrases-replacement.txt
23 directories, 182 files