From ade4d1d3780df2f1610963ef69382b544150bc6d Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sun, 3 Apr 2022 20:02:39 +0800 Subject: [PATCH 01/16] Gitignore // Add cases imported from Visual Studio. --- .gitignore | 217 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 216 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index d68d256a..62d72749 100644 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1,229 @@ +!**/[Pp]ackages/build/ +!*.[Cc]ache/ +!.axoCover/settings.json +$RECYCLE.BIN/ +$tf/ +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.HTMLClient/GeneratedArtifacts +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +**/[Pp]ackages/* +*.app +*.appx +*.aps +*.azurePubxml +*.bim.layout +*.bim_*.settings +*.binlog +*.btm.cs +*.btp.cs +*.build.csdef +*.cab +*.cachefile +*.coverage +*.coveragexml +*.dbmdl +*.dbproj.schemaview *.dmg +*.dotCover +*.DotSettings.user +*.e2e +*.GhostDoc.xml +*.gpState +*.ilk +*.iobj +*.ipdb +*.jfm +*.jmconfig +*.ldf +*.lnk +*.log +*.mdf +*.meta +*.mm.* *.mode1v3 +*.msi +*.msix +*.msm +*.msp +*.ncb +*.ndf +*.nuget.props +*.nuget.targets +*.nupkg +*.nvuser +*.obj +*.odx.cs +*.opendb +*.opensdf +*.opt *.pbxuser +*.pch +*.pdb +*.pfx +*.pgc +*.pgd +*.pidb +*.plg +*.psess +*.publishproj +*.publishsettings +*.pubxml +*.pyc +*.rdl.data +*.rptproj.bak +*.rptproj.rsuser +*.rsp +*.sap +*.sbr +*.scc +*.sdf +*.sln.docstates +*.sln.iml +*.stackdump +*.suo +*.svclog +*.tlb +*.tlh +*.tli +*.tmp +*.tmp_proj *.tm_build_errors +*.tss +*.user +*.userosscache +*.userprefs +*.usertasks +*.vbw +*.VC.db +*.VC.VC.opendb +*.VisualState.xml +*.vsp +*.vspscc +*.vspx +*.vssscc +*.xsd.cs +*.[Cc]ache +*.[Pp]ublish.xml +*.[Rr]e[Ss]harper +*_h.h +*_i.c +*_p.c +*_wpftmp.csproj +*~ +.*crunch*.local.xml +.apdisk +.AppleDB +.AppleDesktop +.AppleDouble +.axoCover/* .build +.builds +.com.apple.timemachine.donotpresent +.cr/personal +.DocumentRevisions-V100 .DS_Store +.fake/ +.fseventsd .idea +.idea/ +.JustCode +.localhistory/ +.LSOverride +.mfractor/ +.ntvs_analysis.dat +.paket/paket.exe +.sass-cache/ +.Spotlight-V100 .swiftpm +.TemporaryItems +.Trashes +.VolumeIcon.icns +.vs/ .vscode +._* +aclocal.m4 +AppPackages/ +artifacts/ +ASALocalRun/ +autom4te.cache/ +AutoTest.Net/ +Backup*/ +BenchmarkDotNet.Artifacts/ +bld/ build +BundleArtifacts/ +ClientBin/ +config.make +config.status Credits.rtf +csx/ +dlldata.c +DocProject/buildhelp/ +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/*.HxC +DocProject/Help/*.HxT +DocProject/Help/html +DocProject/Help/Html2 +ecf/ +ehthumbs.db +ehthumbs_vista.db +FakesAssemblies/ +Generated\ Files/ +Generated_Code/ +Icon +install-sh +ipch/ +Makefile.in +nCrunchTemp_* +Network Trash Folder +node_modules/ +OpenCover/ +orleans.codegen.cs +Package.StoreAssociation.xml +paket-files/ +project.fragment.lock.json +project.lock.json project.xcworkspace +publish/ +PublishScripts/ +rcf/ +ServiceFabricBackup/ Source/Data/* -xcuserdata \ No newline at end of file +StyleCopReport.xml +tarballs/ +Temporary Items +test-results/ +TestResult.xml +Thumbs.db +UpgradeLog*.htm +UpgradeLog*.XML +x64/ +x86/ +xcuserdata +[Bb]in/ +[Bb]uild +[Bb]uild[Ll]og.* +[Dd]ebug/ +[Dd]ebugPS/ +[Dd]ebugPublic/ +[Dd]esktop.ini +[Ee]xpress/ +[Ll]og/ +[Oo]bj/ +[Rr]elease/ +[Rr]eleasePS/ +[Rr]eleases/ +[Tt]est[Rr]esult*/ +_Chutzpah* +_NCrunch_* +_pkginfo.txt +_Pvt_Extensions +_ReSharper*/ +_TeamCity* +_UpgradeReport_Files/ +__pycache__/ +~$* \ No newline at end of file From 2472af883c067f0594aaf2618263d02cf39070c2 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sun, 3 Apr 2022 18:59:25 +0800 Subject: [PATCH 02/16] Git // Add backup of the git config file. --- .gitconfig_backup | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .gitconfig_backup diff --git a/.gitconfig_backup b/.gitconfig_backup new file mode 100644 index 00000000..6aa489d4 --- /dev/null +++ b/.gitconfig_backup @@ -0,0 +1,39 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = false + precomposeunicode = true +[submodule] + active = . +[remote "origin"] + url = https://gitee.com/vchewing/vChewing-macOS + fetch = +refs/heads/*:refs/remotes/origin/* +[branch "main"] + remote = origin + merge = refs/heads/main +[submodule "Source/Data"] + url = https://gitee.com/vchewing/libvchewing-data +[branch "upd/dev"] + remote = origin + merge = refs/heads/upd/dev +[branch "bleed/1.5.x"] + remote = origin + merge = refs/heads/bleed/1.5.x +[remote "gitcode"] + url = https://gitcode.net/vChewing/vChewing-macOS.git/ + fetch = +refs/heads/*:refs/remotes/gitcode/* +[remote "gitlab"] + url = https://jihulab.com/vChewing/vChewing-macOS.git/ + fetch = +refs/heads/*:refs/remotes/gitlab/* +[remote "github"] + url = https://github.com/ShikiSuen//vChewing-macOS + fetch = +refs/heads/*:refs/remotes/github/* +[remote "all"] + url = https://gitee.com/vchewing/vChewing-macOS + fetch = +refs/heads/*:refs/remotes/all/* + pushurl = https://gitee.com/vchewing/vChewing-macOS + pushurl = https://gitcode.net/vChewing/vChewing-macOS.git/ + pushurl = https://jihulab.com/vChewing/vChewing-macOS.git/ + pushurl = https://github.com/ShikiSuen//vChewing-macOS From 9aa781ecfd8f193006cf66ac3b0f18d2f0401864 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sun, 3 Apr 2022 19:04:36 +0800 Subject: [PATCH 03/16] Makefile // Allow git garbage collection. --- Makefile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Makefile b/Makefile index 8e82984e..5bb75a65 100644 --- a/Makefile +++ b/Makefile @@ -37,3 +37,8 @@ clean: xcodebuild -scheme vChewingInstaller -configuration Debug $(BUILD_SETTINGS) clean xcodebuild -scheme vChewingInstaller -configuration Release $(BUILD_SETTINGS) clean make clean --file=./Source/Data/Makefile || true + +.PHONY: gc + +gc: + git reflog expire --expire=now --all && git gc --prune=now --aggressive From 5fba88cfeb4f63d3bf9efc59d8e0f6893c4ca86a Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sun, 3 Apr 2022 12:50:51 +0800 Subject: [PATCH 04/16] OpenCC // Swift Clang-Format. --- Packages/SwiftyOpenCC/Package.swift | 158 +++++++-------- .../Sources/OpenCC/ChineseConverter.swift | 126 ++++++------ .../Sources/OpenCC/ConversionDictionary.swift | 36 ++-- .../Sources/OpenCC/ConversionError.swift | 50 ++--- .../Sources/OpenCC/DictionaryLoader.swift | 84 ++++---- .../Sources/OpenCC/DictionaryName.swift | 184 +++++++++--------- .../Sources/OpenCC/WeakValueCache.swift | 56 +++--- .../Tests/OpenCCTests/OpenCCTests.swift | 115 +++++------ 8 files changed, 408 insertions(+), 401 deletions(-) diff --git a/Packages/SwiftyOpenCC/Package.swift b/Packages/SwiftyOpenCC/Package.swift index af2c4e23..115ce9e0 100644 --- a/Packages/SwiftyOpenCC/Package.swift +++ b/Packages/SwiftyOpenCC/Package.swift @@ -3,83 +3,83 @@ import PackageDescription let package = Package( - name: "SwiftyOpenCC", - products: [ - .library( - name: "OpenCC", - targets: ["OpenCC"]), - ], - targets: [ - .target( - name: "OpenCC", - dependencies: ["copencc"], - resources: [ - .copy("Dictionary") - ]), - .testTarget( - name: "OpenCCTests", - dependencies: ["OpenCC"], - resources: [ - .copy("benchmark"), - .copy("testcases"), - ]), - .target( - name: "copencc", - exclude: [ - "src/benchmark", - "src/tools", - "src/BinaryDictTest.cpp", - "src/Config.cpp", - "src/ConfigTest.cpp", - "src/ConversionChainTest.cpp", - "src/ConversionTest.cpp", - "src/DartsDictTest.cpp", - "src/DictGroupTest.cpp", - "src/MarisaDictTest.cpp", - "src/MaxMatchSegmentationTest.cpp", - "src/PhraseExtractTest.cpp", - "src/SerializedValuesTest.cpp", - "src/SimpleConverter.cpp", - "src/SimpleConverterTest.cpp", - "src/TextDictTest.cpp", - "src/UTF8StringSliceTest.cpp", - "src/UTF8UtilTest.cpp", - "deps/google-benchmark", - "deps/gtest-1.11.0", - "deps/pybind11-2.5.0", - "deps/rapidjson-1.1.0", - "deps/tclap-1.2.2", - - "src/CmdLineOutput.hpp", - "src/Config.hpp", - "src/ConfigTestBase.hpp", - "src/DictGroupTestBase.hpp", - "src/SimpleConverter.hpp", - "src/TestUtils.hpp", - "src/TestUtilsUTF8.hpp", - "src/TextDictTestBase.hpp", - "src/py_opencc.cpp", - - // ??? - "src/README.md", - "src/CMakeLists.txt", - "deps/marisa-0.2.6/AUTHORS", - "deps/marisa-0.2.6/CMakeLists.txt", - "deps/marisa-0.2.6/COPYING.md", - "deps/marisa-0.2.6/README.md", - ], - sources: [ - "source.cpp", - "src", - "deps/marisa-0.2.6", - ], - cxxSettings: [ - .headerSearchPath("src"), - .headerSearchPath("deps/darts-clone"), - .headerSearchPath("deps/marisa-0.2.6/include"), - .headerSearchPath("deps/marisa-0.2.6/lib"), - .define("ENABLE_DARTS"), - ]), - ], - cxxLanguageStandard: .cxx14 + name: "SwiftyOpenCC", + products: [ + .library( + name: "OpenCC", + targets: ["OpenCC"]) + ], + targets: [ + .target( + name: "OpenCC", + dependencies: ["copencc"], + resources: [ + .copy("Dictionary") + ]), + .testTarget( + name: "OpenCCTests", + dependencies: ["OpenCC"], + resources: [ + .copy("benchmark"), + .copy("testcases"), + ]), + .target( + name: "copencc", + exclude: [ + "src/benchmark", + "src/tools", + "src/BinaryDictTest.cpp", + "src/Config.cpp", + "src/ConfigTest.cpp", + "src/ConversionChainTest.cpp", + "src/ConversionTest.cpp", + "src/DartsDictTest.cpp", + "src/DictGroupTest.cpp", + "src/MarisaDictTest.cpp", + "src/MaxMatchSegmentationTest.cpp", + "src/PhraseExtractTest.cpp", + "src/SerializedValuesTest.cpp", + "src/SimpleConverter.cpp", + "src/SimpleConverterTest.cpp", + "src/TextDictTest.cpp", + "src/UTF8StringSliceTest.cpp", + "src/UTF8UtilTest.cpp", + "deps/google-benchmark", + "deps/gtest-1.11.0", + "deps/pybind11-2.5.0", + "deps/rapidjson-1.1.0", + "deps/tclap-1.2.2", + + "src/CmdLineOutput.hpp", + "src/Config.hpp", + "src/ConfigTestBase.hpp", + "src/DictGroupTestBase.hpp", + "src/SimpleConverter.hpp", + "src/TestUtils.hpp", + "src/TestUtilsUTF8.hpp", + "src/TextDictTestBase.hpp", + "src/py_opencc.cpp", + + // ??? + "src/README.md", + "src/CMakeLists.txt", + "deps/marisa-0.2.6/AUTHORS", + "deps/marisa-0.2.6/CMakeLists.txt", + "deps/marisa-0.2.6/COPYING.md", + "deps/marisa-0.2.6/README.md", + ], + sources: [ + "source.cpp", + "src", + "deps/marisa-0.2.6", + ], + cxxSettings: [ + .headerSearchPath("src"), + .headerSearchPath("deps/darts-clone"), + .headerSearchPath("deps/marisa-0.2.6/include"), + .headerSearchPath("deps/marisa-0.2.6/lib"), + .define("ENABLE_DARTS"), + ]), + ], + cxxLanguageStandard: .cxx14 ) diff --git a/Packages/SwiftyOpenCC/Sources/OpenCC/ChineseConverter.swift b/Packages/SwiftyOpenCC/Sources/OpenCC/ChineseConverter.swift index 259ed968..10cea288 100644 --- a/Packages/SwiftyOpenCC/Sources/OpenCC/ChineseConverter.swift +++ b/Packages/SwiftyOpenCC/Sources/OpenCC/ChineseConverter.swift @@ -22,68 +22,68 @@ import copencc /// However, the string on which it is operating should not be mutated /// during the course of a conversion. public class ChineseConverter { - - /// These constants define the ChineseConverter options. - public struct Options: OptionSet { - - public let rawValue: Int - - public init(rawValue: Int) { - self.rawValue = rawValue - } - - /// Convert to Traditional Chinese. (default) - public static let traditionalize = Options(rawValue: 1 << 0) - - /// Convert to Simplified Chinese. - public static let simplify = Options(rawValue: 1 << 1) - - /// Use Taiwan standard. - public static let twStandard = Options(rawValue: 1 << 5) - - /// Use HongKong standard. - public static let hkStandard = Options(rawValue: 1 << 6) - - /// Cancel Taiwan standard. - public static let twStandardRev = Options(rawValue: 1 << 15) - - /// Cancel HongKong standard. - public static let hkStandardRev = Options(rawValue: 1 << 16) - /// Taiwanese idiom conversion. - public static let twIdiom = Options(rawValue: 1 << 10) - } - - private let seg: ConversionDictionary - private let chain: [ConversionDictionary] - - private let converter: CCConverterRef - - private init(loader: DictionaryLoader, options: Options) throws { - seg = try loader.segmentation(options: options) - chain = try loader.conversionChain(options: options) - var rawChain = chain.map { $0.dict } - converter = CCConverterCreate("SwiftyOpenCC", seg.dict, &rawChain, rawChain.count) - } - - /// Returns an initialized `ChineseConverter` instance with the specified - /// conversion options. - /// - /// - Parameter options: The convert’s options. - /// - Throws: Throws `ConversionError` if failed. - public convenience init(options: Options) throws { - let loader = DictionaryLoader(bundle: .module) - try self.init(loader: loader, options: options) - } - - /// Return a converted string using the convert’s current option. - /// - /// - Parameter text: The string to convert. - /// - Returns: A converted string using the convert’s current option. - public func convert(_ text: String) -> String { - let stlStr = CCConverterCreateConvertedStringFromString(converter, text)! - defer { STLStringDestroy(stlStr) } - return String(utf8String: STLStringGetUTF8String(stlStr))! - } - + /// These constants define the ChineseConverter options. + public struct Options: OptionSet { + + public let rawValue: Int + + public init(rawValue: Int) { + self.rawValue = rawValue + } + + /// Convert to Traditional Chinese. (default) + public static let traditionalize = Options(rawValue: 1 << 0) + + /// Convert to Simplified Chinese. + public static let simplify = Options(rawValue: 1 << 1) + + /// Use Taiwan standard. + public static let twStandard = Options(rawValue: 1 << 5) + + /// Use HongKong standard. + public static let hkStandard = Options(rawValue: 1 << 6) + + /// Cancel Taiwan standard. + public static let twStandardRev = Options(rawValue: 1 << 15) + + /// Cancel HongKong standard. + public static let hkStandardRev = Options(rawValue: 1 << 16) + + /// Taiwanese idiom conversion. + public static let twIdiom = Options(rawValue: 1 << 10) + } + + private let seg: ConversionDictionary + private let chain: [ConversionDictionary] + + private let converter: CCConverterRef + + private init(loader: DictionaryLoader, options: Options) throws { + seg = try loader.segmentation(options: options) + chain = try loader.conversionChain(options: options) + var rawChain = chain.map { $0.dict } + converter = CCConverterCreate("SwiftyOpenCC", seg.dict, &rawChain, rawChain.count) + } + + /// Returns an initialized `ChineseConverter` instance with the specified + /// conversion options. + /// + /// - Parameter options: The convert’s options. + /// - Throws: Throws `ConversionError` if failed. + public convenience init(options: Options) throws { + let loader = DictionaryLoader(bundle: .module) + try self.init(loader: loader, options: options) + } + + /// Return a converted string using the convert’s current option. + /// + /// - Parameter text: The string to convert. + /// - Returns: A converted string using the convert’s current option. + public func convert(_ text: String) -> String { + let stlStr = CCConverterCreateConvertedStringFromString(converter, text)! + defer { STLStringDestroy(stlStr) } + return String(utf8String: STLStringGetUTF8String(stlStr))! + } + } diff --git a/Packages/SwiftyOpenCC/Sources/OpenCC/ConversionDictionary.swift b/Packages/SwiftyOpenCC/Sources/OpenCC/ConversionDictionary.swift index c91d515f..df7755ff 100644 --- a/Packages/SwiftyOpenCC/Sources/OpenCC/ConversionDictionary.swift +++ b/Packages/SwiftyOpenCC/Sources/OpenCC/ConversionDictionary.swift @@ -9,22 +9,22 @@ import Foundation import copencc class ConversionDictionary { - - let group: [ConversionDictionary] - - let dict: CCDictRef - - init(path: String) throws { - guard let dict = CCDictCreateMarisaWithPath(path) else { - throw ConversionError(ccErrorno) - } - self.group = [] - self.dict = dict - } - - init(group: [ConversionDictionary]) { - var rawGroup = group.map { $0.dict } - self.group = group - self.dict = CCDictCreateWithGroup(&rawGroup, rawGroup.count) - } + + let group: [ConversionDictionary] + + let dict: CCDictRef + + init(path: String) throws { + guard let dict = CCDictCreateMarisaWithPath(path) else { + throw ConversionError(ccErrorno) + } + self.group = [] + self.dict = dict + } + + init(group: [ConversionDictionary]) { + var rawGroup = group.map { $0.dict } + self.group = group + self.dict = CCDictCreateWithGroup(&rawGroup, rawGroup.count) + } } diff --git a/Packages/SwiftyOpenCC/Sources/OpenCC/ConversionError.swift b/Packages/SwiftyOpenCC/Sources/OpenCC/ConversionError.swift index d0114359..d24f1f21 100644 --- a/Packages/SwiftyOpenCC/Sources/OpenCC/ConversionError.swift +++ b/Packages/SwiftyOpenCC/Sources/OpenCC/ConversionError.swift @@ -9,29 +9,29 @@ import Foundation import copencc public enum ConversionError: Error { - - case fileNotFound - - case invalidFormat - - case invalidTextDictionary - - case invalidUTF8 - - case unknown - - init(_ code: CCErrorCode) { - switch code { - case .fileNotFound: - self = .fileNotFound - case .invalidFormat: - self = .invalidFormat - case .invalidTextDictionary: - self = .invalidTextDictionary - case .invalidUTF8: - self = .invalidUTF8 - case .unknown, _: - self = .unknown - } - } + + case fileNotFound + + case invalidFormat + + case invalidTextDictionary + + case invalidUTF8 + + case unknown + + init(_ code: CCErrorCode) { + switch code { + case .fileNotFound: + self = .fileNotFound + case .invalidFormat: + self = .invalidFormat + case .invalidTextDictionary: + self = .invalidTextDictionary + case .invalidUTF8: + self = .invalidUTF8 + case .unknown, _: + self = .unknown + } + } } diff --git a/Packages/SwiftyOpenCC/Sources/OpenCC/DictionaryLoader.swift b/Packages/SwiftyOpenCC/Sources/OpenCC/DictionaryLoader.swift index d97b11ef..83dcc9db 100644 --- a/Packages/SwiftyOpenCC/Sources/OpenCC/DictionaryLoader.swift +++ b/Packages/SwiftyOpenCC/Sources/OpenCC/DictionaryLoader.swift @@ -9,47 +9,51 @@ import Foundation import copencc extension ChineseConverter { - - struct DictionaryLoader { - - private static let subdirectory = "Dictionary" - private static let dictCache = WeakValueCache() - - private let bundle: Bundle - - init(bundle: Bundle) { - self.bundle = bundle - } - - func dict(_ name: ChineseConverter.DictionaryName) throws -> ConversionDictionary { - guard let path = bundle.path(forResource: name.description, ofType: "ocd2", inDirectory: DictionaryLoader.subdirectory) else { - throw ConversionError.fileNotFound - } - return try DictionaryLoader.dictCache.value(for: path) { - return try ConversionDictionary(path: path) - } - } - } + + struct DictionaryLoader { + + private static let subdirectory = "Dictionary" + private static let dictCache = WeakValueCache() + + private let bundle: Bundle + + init(bundle: Bundle) { + self.bundle = bundle + } + + func dict(_ name: ChineseConverter.DictionaryName) throws -> ConversionDictionary { + guard + let path = bundle.path( + forResource: name.description, ofType: "ocd2", + inDirectory: DictionaryLoader.subdirectory) + else { + throw ConversionError.fileNotFound + } + return try DictionaryLoader.dictCache.value(for: path) { + return try ConversionDictionary(path: path) + } + } + } } extension ChineseConverter.DictionaryLoader { - - func segmentation(options: ChineseConverter.Options) throws -> ConversionDictionary { - let dictName = options.segmentationDictName - return try dict(dictName) - } - - func conversionChain(options: ChineseConverter.Options) throws -> [ConversionDictionary] { - return try options.conversionChain.compactMap { names in - switch names.count { - case 0: - return nil - case 1: - return try dict(names.first!) - case _: - let dicts = try names.map(dict) - return ConversionDictionary(group: dicts) - } - } - } + + func segmentation(options: ChineseConverter.Options) throws -> ConversionDictionary { + let dictName = options.segmentationDictName + return try dict(dictName) + } + + func conversionChain(options: ChineseConverter.Options) throws -> [ConversionDictionary] { + return try options.conversionChain.compactMap { names in + switch names.count { + case 0: + return nil + case 1: + return try dict(names.first!) + case _: + let dicts = try names.map(dict) + return ConversionDictionary(group: dicts) + } + } + } } diff --git a/Packages/SwiftyOpenCC/Sources/OpenCC/DictionaryName.swift b/Packages/SwiftyOpenCC/Sources/OpenCC/DictionaryName.swift index 6e7f2e4d..3629bbcc 100644 --- a/Packages/SwiftyOpenCC/Sources/OpenCC/DictionaryName.swift +++ b/Packages/SwiftyOpenCC/Sources/OpenCC/DictionaryName.swift @@ -8,99 +8,99 @@ import Foundation extension ChineseConverter { - - enum DictionaryName: CustomStringConvertible { - - case hkVariants - case hkVariantsRev - case hkVariantsRevPhrases - case jpVariants - case stCharacters - case stPhrases - case tsCharacters - case tsPhrases - case twPhrases - case twPhrasesRev - case twVariants - case twVariantsRev - case twVariantsRevPhrases - - var description: String { - switch self { - case .hkVariants: return "HKVariants" - case .hkVariantsRev: return "HKVariantsRev" - case .hkVariantsRevPhrases: return "HKVariantsRevPhrases" - case .jpVariants: return "JPVariants" - case .stCharacters: return "STCharacters" - case .stPhrases: return "STPhrases" - case .tsCharacters: return "TSCharacters" - case .tsPhrases: return "TSPhrases" - case .twPhrases: return "TWPhrases" - case .twPhrasesRev: return "TWPhrasesRev" - case .twVariants: return "TWVariants" - case .twVariantsRev: return "TWVariantsRev" - case .twVariantsRevPhrases: return "TWVariantsRevPhrases" - } - } - } + + enum DictionaryName: CustomStringConvertible { + + case hkVariants + case hkVariantsRev + case hkVariantsRevPhrases + case jpVariants + case stCharacters + case stPhrases + case tsCharacters + case tsPhrases + case twPhrases + case twPhrasesRev + case twVariants + case twVariantsRev + case twVariantsRevPhrases + + var description: String { + switch self { + case .hkVariants: return "HKVariants" + case .hkVariantsRev: return "HKVariantsRev" + case .hkVariantsRevPhrases: return "HKVariantsRevPhrases" + case .jpVariants: return "JPVariants" + case .stCharacters: return "STCharacters" + case .stPhrases: return "STPhrases" + case .tsCharacters: return "TSCharacters" + case .tsPhrases: return "TSPhrases" + case .twPhrases: return "TWPhrases" + case .twPhrasesRev: return "TWPhrasesRev" + case .twVariants: return "TWVariants" + case .twVariantsRev: return "TWVariantsRev" + case .twVariantsRevPhrases: return "TWVariantsRevPhrases" + } + } + } } extension ChineseConverter.Options { - - var segmentationDictName: ChineseConverter.DictionaryName { - if contains(.traditionalize) { - return .stPhrases - } else if contains(.simplify) { - return .tsPhrases - } else if contains(.hkStandard) { - return .hkVariants - } else if contains(.twStandard) { - return .twVariants - } else if contains(.hkStandardRev) { - return .hkVariantsRev - } else if contains(.twStandardRev) { - return .twVariantsRev - } else { - return .stPhrases - } - } - - var conversionChain: [[ChineseConverter.DictionaryName]] { - var result: [[ChineseConverter.DictionaryName]] = [] - if contains(.traditionalize) { - result.append([.stPhrases, .stCharacters]) - if contains(.twIdiom) { - result.append([.twPhrases]) - } - if contains(.hkStandard) { - result.append([.hkVariants]) - } else if contains(.twStandard) { - result.append([.twVariants]) - } - } else if contains(.simplify) { - if contains(.hkStandard) { - result.append([.hkVariantsRevPhrases, .hkVariantsRev]) - } else if contains(.twStandard) { - result.append([.twVariantsRevPhrases, .twVariantsRev]) - } - if contains(.twIdiom) { - result.append([.twPhrasesRev]) - } - result.append([.tsPhrases, .tsCharacters]) - } else { - if contains(.hkStandard) { - result.append([.hkVariants]) - } else if contains(.twStandard) { - result.append([.twVariants]) - } else if contains(.hkStandardRev) { - result.append([.hkVariantsRev]) - } else if contains(.twStandardRev) { - result.append([.twVariantsRev]) - } - } - if result.isEmpty { - return [[.stPhrases, .stCharacters]] - } - return result - } + + var segmentationDictName: ChineseConverter.DictionaryName { + if contains(.traditionalize) { + return .stPhrases + } else if contains(.simplify) { + return .tsPhrases + } else if contains(.hkStandard) { + return .hkVariants + } else if contains(.twStandard) { + return .twVariants + } else if contains(.hkStandardRev) { + return .hkVariantsRev + } else if contains(.twStandardRev) { + return .twVariantsRev + } else { + return .stPhrases + } + } + + var conversionChain: [[ChineseConverter.DictionaryName]] { + var result: [[ChineseConverter.DictionaryName]] = [] + if contains(.traditionalize) { + result.append([.stPhrases, .stCharacters]) + if contains(.twIdiom) { + result.append([.twPhrases]) + } + if contains(.hkStandard) { + result.append([.hkVariants]) + } else if contains(.twStandard) { + result.append([.twVariants]) + } + } else if contains(.simplify) { + if contains(.hkStandard) { + result.append([.hkVariantsRevPhrases, .hkVariantsRev]) + } else if contains(.twStandard) { + result.append([.twVariantsRevPhrases, .twVariantsRev]) + } + if contains(.twIdiom) { + result.append([.twPhrasesRev]) + } + result.append([.tsPhrases, .tsCharacters]) + } else { + if contains(.hkStandard) { + result.append([.hkVariants]) + } else if contains(.twStandard) { + result.append([.twVariants]) + } else if contains(.hkStandardRev) { + result.append([.hkVariantsRev]) + } else if contains(.twStandardRev) { + result.append([.twVariantsRev]) + } + } + if result.isEmpty { + return [[.stPhrases, .stCharacters]] + } + return result + } } diff --git a/Packages/SwiftyOpenCC/Sources/OpenCC/WeakValueCache.swift b/Packages/SwiftyOpenCC/Sources/OpenCC/WeakValueCache.swift index 648361fc..d945a371 100644 --- a/Packages/SwiftyOpenCC/Sources/OpenCC/WeakValueCache.swift +++ b/Packages/SwiftyOpenCC/Sources/OpenCC/WeakValueCache.swift @@ -8,35 +8,35 @@ import Foundation class WeakBox { - - private(set) weak var value: Value? - - init(_ value: Value) { - self.value = value - } + + private(set) weak var value: Value? + + init(_ value: Value) { + self.value = value + } } class WeakValueCache { - - private var storage: [Key: WeakBox] = [:] - - private var lock = NSLock() - - func value(for key: Key) -> Value? { - return storage[key]?.value - } - - func value(for key: Key, make: () throws -> Value) rethrows -> Value { - if let value = storage[key]?.value { - return value - } - lock.lock() - defer { lock.unlock() } - if let value = storage[key]?.value { - return value - } - let value = try make() - storage[key] = WeakBox(value) - return value - } + + private var storage: [Key: WeakBox] = [:] + + private var lock = NSLock() + + func value(for key: Key) -> Value? { + return storage[key]?.value + } + + func value(for key: Key, make: () throws -> Value) rethrows -> Value { + if let value = storage[key]?.value { + return value + } + lock.lock() + defer { lock.unlock() } + if let value = storage[key]?.value { + return value + } + let value = try make() + storage[key] = WeakBox(value) + return value + } } diff --git a/Packages/SwiftyOpenCC/Tests/OpenCCTests/OpenCCTests.swift b/Packages/SwiftyOpenCC/Tests/OpenCCTests/OpenCCTests.swift index 03c7d093..6ce821cc 100644 --- a/Packages/SwiftyOpenCC/Tests/OpenCCTests/OpenCCTests.swift +++ b/Packages/SwiftyOpenCC/Tests/OpenCCTests/OpenCCTests.swift @@ -1,64 +1,67 @@ import XCTest + @testable import OpenCC let testCases: [(String, ChineseConverter.Options)] = [ - ("s2t", [.traditionalize]), - ("t2s", [.simplify]), - ("s2hk", [.traditionalize, .hkStandard]), - ("hk2s", [.simplify, .hkStandard]), - ("s2tw", [.traditionalize, .twStandard]), - ("tw2s", [.simplify, .twStandard]), - ("s2twp", [.traditionalize, .twStandard, .twIdiom]), - ("tw2sp", [.simplify, .twStandard, .twIdiom]), + ("s2t", [.traditionalize]), + ("t2s", [.simplify]), + ("s2hk", [.traditionalize, .hkStandard]), + ("hk2s", [.simplify, .hkStandard]), + ("s2tw", [.traditionalize, .twStandard]), + ("tw2s", [.simplify, .twStandard]), + ("s2twp", [.traditionalize, .twStandard, .twIdiom]), + ("tw2sp", [.simplify, .twStandard, .twIdiom]), ] class OpenCCTests: XCTestCase { - - func converter(option: ChineseConverter.Options) throws -> ChineseConverter { - return try ChineseConverter(options: option) - } - - func testConversion() throws { - func testCase(name: String, ext: String) -> String { - let url = Bundle.module.url(forResource: name, withExtension: ext, subdirectory: "testcases")! - return try! String(contentsOf: url) - } - for (name, opt) in testCases { - let coverter = try ChineseConverter(options: opt) - let input = testCase(name: name, ext: "in") - let converted = coverter.convert(input) - let output = testCase(name: name, ext: "ans") - XCTAssertEqual(converted, output, "Conversion \(name) fails") - } - } - - func testConverterCreationPerformance() { - let options: ChineseConverter.Options = [.traditionalize, .twStandard, .twIdiom] - measure { - for _ in 0..<10 { - _ = try! ChineseConverter(options: options) - } - } - } - - func testDictionaryCache() { - let options: ChineseConverter.Options = [.traditionalize, .twStandard, .twIdiom] - let holder = try! ChineseConverter(options: options) - measure { - for _ in 0..<1_000 { - _ = try! ChineseConverter(options: options) - } - } - _ = holder.convert("foo") - } - - func testConversionPerformance() throws { - let cov = try converter(option: [.traditionalize, .twStandard, .twIdiom]) - let url = Bundle.module.url(forResource: "zuozhuan", withExtension: "txt", subdirectory: "benchmark")! - // 1.9 MB, 624k word - let str = try String(contentsOf: url) - measure { - _ = cov.convert(str) - } - } + + func converter(option: ChineseConverter.Options) throws -> ChineseConverter { + return try ChineseConverter(options: option) + } + + func testConversion() throws { + func testCase(name: String, ext: String) -> String { + let url = Bundle.module.url( + forResource: name, withExtension: ext, subdirectory: "testcases")! + return try! String(contentsOf: url) + } + for (name, opt) in testCases { + let coverter = try ChineseConverter(options: opt) + let input = testCase(name: name, ext: "in") + let converted = coverter.convert(input) + let output = testCase(name: name, ext: "ans") + XCTAssertEqual(converted, output, "Conversion \(name) fails") + } + } + + func testConverterCreationPerformance() { + let options: ChineseConverter.Options = [.traditionalize, .twStandard, .twIdiom] + measure { + for _ in 0..<10 { + _ = try! ChineseConverter(options: options) + } + } + } + + func testDictionaryCache() { + let options: ChineseConverter.Options = [.traditionalize, .twStandard, .twIdiom] + let holder = try! ChineseConverter(options: options) + measure { + for _ in 0..<1_000 { + _ = try! ChineseConverter(options: options) + } + } + _ = holder.convert("foo") + } + + func testConversionPerformance() throws { + let cov = try converter(option: [.traditionalize, .twStandard, .twIdiom]) + let url = Bundle.module.url( + forResource: "zuozhuan", withExtension: "txt", subdirectory: "benchmark")! + // 1.9 MB, 624k word + let str = try String(contentsOf: url) + measure { + _ = cov.convert(str) + } + } } From a87191ea2e113805ed1cbb01e289ea4ab26e7632 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sun, 3 Apr 2022 18:36:15 +0800 Subject: [PATCH 05/16] AUTHORS // Initiate authors file. --- AUTHORS | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 AUTHORS diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 00000000..158911b6 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,11 @@ +$ Main contributors and volunteers of this repository (vChewing for macOS): + +Shiki Suen // Main developer of vChewing for macOS. +Hiraku Wang // Technical assistant in Cocoa. + + +$ Contributors and volunteeres of the upstream repo, having no responsibility in discussing anything in the current repo: + +Mengjuei Hsieh // McBopomofo for macOS 1.x main developer and architect. +Zonble Yang // McBopomofo for macOS 2.x architect. +Lukhnos D Liu // Mandarin and Gramambular engine developer. From 5af66f7480ea117553a08a6a0fef93e7f0a0dbda Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sun, 3 Apr 2022 11:23:50 +0800 Subject: [PATCH 06/16] Swift // Clang-Format. --- .clang-format-swift.json | 56 + DataCompiler/dataCompiler.swift | 668 ++++---- Installer/AppDelegate.swift | 518 +++--- Installer/ArchiveUtil.swift | 207 +-- .../3rdParty/OpenCCBridge/OpenCCBridge.swift | 77 +- Source/Modules/AppDelegate.swift | 588 ++++--- .../AppleKeyboardConverter.swift | 644 +++---- .../ControllerModules/InputState.swift | 698 ++++---- .../Modules/ControllerModules/KeyParser.swift | 445 ++--- .../ControllerModules/NSStringUtils.swift | 115 +- .../vChewingKanjiConverter.swift | 1516 +++++++++-------- .../FileHandlers/FSEventStreamHelper.swift | 156 +- Source/Modules/IME.swift | 307 ++-- .../IMEModules/InputSourceHelper.swift | 217 +-- .../Modules/IMEModules/ctlInputMethod.swift | 1437 +++++++++------- Source/Modules/IMEModules/mgrPrefs.swift | 935 +++++----- .../Modules/LangModelRelated/mgrLangModel.mm | 2 +- Source/Modules/SFX/clsSFX.swift | 100 +- Source/Modules/main.swift | 63 +- .../UI/CandidateUI/CandidateController.swift | 263 +-- .../HorizontalCandidateController.swift | 741 ++++---- .../VerticalCandidateController.swift | 750 ++++---- Source/UI/NotifierUI/NotifierController.swift | 333 ++-- Source/UI/TooltipUI/TooltipController.swift | 191 ++- Source/WindowControllers/ctlAboutWindow.swift | 89 +- .../ctlNonModalAlertWindow.swift | 188 +- Source/WindowControllers/ctlPrefWindow.swift | 480 +++--- UserPhraseEditor/AppDelegate.swift | 78 +- UserPhraseEditor/Content.swift | 60 +- UserPhraseEditor/Document.swift | 254 +-- UserPhraseEditor/StringExtension.swift | 1016 +++++------ UserPhraseEditor/ViewController.swift | 86 +- UserPhraseEditor/WindowController.swift | 50 +- UserPhraseEditor/ctlAboutWindow.swift | 89 +- 34 files changed, 7137 insertions(+), 6280 deletions(-) create mode 100644 .clang-format-swift.json diff --git a/.clang-format-swift.json b/.clang-format-swift.json new file mode 100644 index 00000000..7c9dc985 --- /dev/null +++ b/.clang-format-swift.json @@ -0,0 +1,56 @@ +{ + "fileScopedDeclarationPrivacy" : { + "accessLevel" : "private" + }, + "indentation" : { + "tabs" : 1 + }, + "indentConditionalCompilationBlocks" : true, + "indentSwitchCaseLabels" : true, + "lineBreakAroundMultilineExpressionChainComponents" : false, + "lineBreakBeforeControlFlowKeywords" : false, + "lineBreakBeforeEachArgument" : false, + "lineBreakBeforeEachGenericRequirement" : false, + "lineLength" : 120, + "maximumBlankLines" : 1, + "prioritizeKeepingFunctionOutputTogether" : false, + "respectsExistingLineBreaks" : true, + "rules" : { + "AllPublicDeclarationsHaveDocumentation" : false, + "AlwaysUseLowerCamelCase" : true, + "AmbiguousTrailingClosureOverload" : true, + "BeginDocumentationCommentWithOneLineSummary" : false, + "DoNotUseSemicolons" : true, + "DontRepeatTypeInStaticProperties" : true, + "FileScopedDeclarationPrivacy" : true, + "FullyIndirectEnum" : true, + "GroupNumericLiterals" : true, + "IdentifiersMustBeASCII" : true, + "NeverForceUnwrap" : false, + "NeverUseForceTry" : false, + "NeverUseImplicitlyUnwrappedOptionals" : false, + "NoAccessLevelOnExtensionDeclaration" : true, + "NoBlockComments" : false, + "NoCasesWithOnlyFallthrough" : true, + "NoEmptyTrailingClosureParentheses" : true, + "NoLabelsInCasePatterns" : true, + "NoLeadingUnderscores" : false, + "NoParensAroundConditions" : true, + "NoVoidReturnOnFunctionSignature" : true, + "OneCasePerLine" : true, + "OneVariableDeclarationPerLine" : true, + "OnlyOneTrailingClosureArgument" : true, + "OrderedImports" : true, + "ReturnVoidInsteadOfEmptyTuple" : true, + "UseEarlyExits" : false, + "UseLetInEveryBoundCaseVariable" : true, + "UseShorthandTypeNames" : true, + "UseSingleLinePropertyGetter" : true, + "UseSynthesizedInitializer" : true, + "UseTripleSlashForDocumentationComments" : true, + "UseWhereClausesInForLoops" : false, + "ValidateDocumentationComments" : false + }, + "tabWidth" : 4, + "version" : 1 +} diff --git a/DataCompiler/dataCompiler.swift b/DataCompiler/dataCompiler.swift index bb2fc46d..bc183687 100644 --- a/DataCompiler/dataCompiler.swift +++ b/DataCompiler/dataCompiler.swift @@ -1,383 +1,421 @@ +#!/usr/bin/env swift + // Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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 Foundation // MARK: - 前導工作 -fileprivate extension String { - mutating func regReplace(pattern: String, replaceWith: String = "") { - // Ref: https://stackoverflow.com/a/40993403/4162914 && https://stackoverflow.com/a/71291137/4162914 - do { - let regex = try NSRegularExpression(pattern: pattern, options: [.caseInsensitive, .anchorsMatchLines]) - let range = NSRange(self.startIndex..., in: self) - self = regex.stringByReplacingMatches(in: self, options: [], range: range, withTemplate: replaceWith) - } catch { return } - } +extension String { + fileprivate mutating func regReplace(pattern: String, replaceWith: String = "") { + // Ref: https://stackoverflow.com/a/40993403/4162914 && https://stackoverflow.com/a/71291137/4162914 + do { + let regex = try NSRegularExpression( + pattern: pattern, options: [.caseInsensitive, .anchorsMatchLines]) + let range = NSRange(self.startIndex..., in: self) + self = regex.stringByReplacingMatches( + in: self, options: [], range: range, withTemplate: replaceWith) + } catch { return } + } } -fileprivate func getDocumentsDirectory() -> URL { - let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) - return paths[0] +private func getDocumentsDirectory() -> URL { + let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) + return paths[0] } // MARK: - 引入小數點位數控制函數 // Ref: https://stackoverflow.com/a/32581409/4162914 -fileprivate extension Float { - func rounded(toPlaces places:Int) -> Float { - let divisor = pow(10.0, Float(places)) - return (self * divisor).rounded() / divisor - } +extension Float { + fileprivate func rounded(toPlaces places: Int) -> Float { + let divisor = pow(10.0, Float(places)) + return (self * divisor).rounded() / divisor + } } // MARK: - 引入幂乘函數 // Ref: https://stackoverflow.com/a/41581695/4162914 precedencegroup ExponentiationPrecedence { - associativity: right - higherThan: MultiplicationPrecedence + associativity: right + higherThan: MultiplicationPrecedence } -infix operator ** : ExponentiationPrecedence +infix operator **: ExponentiationPrecedence func ** (_ base: Double, _ exp: Double) -> Double { - return pow(base, exp) + return pow(base, exp) } func ** (_ base: Float, _ exp: Float) -> Float { - return pow(base, exp) + return pow(base, exp) } // MARK: - 定義檔案結構 struct Entry { - var valPhone: String = "" - var valPhrase: String = "" - var valWeight: Float = -1.0 - var valCount: Int = 0 + var valPhone: String = "" + var valPhrase: String = "" + var valWeight: Float = -1.0 + var valCount: Int = 0 } // MARK: - 登記全局根常數變數 -fileprivate let urlCurrentFolder = URL(fileURLWithPath: FileManager.default.currentDirectoryPath) +private let urlCurrentFolder = URL(fileURLWithPath: FileManager.default.currentDirectoryPath) -fileprivate let url_CHS_Custom: String = "./components/chs/phrases-custom-chs.txt" -fileprivate let url_CHS_MCBP: String = "./components/chs/phrases-mcbp-chs.txt" -fileprivate let url_CHS_MOE: String = "./components/chs/phrases-moe-chs.txt" -fileprivate let url_CHS_VCHEW: String = "./components/chs/phrases-vchewing-chs.txt" +private let urlCHSforCustom: String = "./components/chs/phrases-custom-chs.txt" +private let urlCHSforMCBP: String = "./components/chs/phrases-mcbp-chs.txt" +private let urlCHSforMOE: String = "./components/chs/phrases-moe-chs.txt" +private let urlCHSforVCHEW: String = "./components/chs/phrases-vchewing-chs.txt" -fileprivate let url_CHT_Custom: String = "./components/cht/phrases-custom-cht.txt" -fileprivate let url_CHT_MCBP: String = "./components/cht/phrases-mcbp-cht.txt" -fileprivate let url_CHT_MOE: String = "./components/cht/phrases-moe-cht.txt" -fileprivate let url_CHT_VCHEW: String = "./components/cht/phrases-vchewing-cht.txt" +private let urlCHTforCustom: String = "./components/cht/phrases-custom-cht.txt" +private let urlCHTforMCBP: String = "./components/cht/phrases-mcbp-cht.txt" +private let urlCHTforMOE: String = "./components/cht/phrases-moe-cht.txt" +private let urlCHTforVCHEW: String = "./components/cht/phrases-vchewing-cht.txt" -fileprivate let urlKanjiCore: String = "./components/common/char-kanji-core.txt" -fileprivate let urlPunctuation: String = "./components/common/data-punctuations.txt" -fileprivate let urlMiscBPMF: String = "./components/common/char-misc-bpmf.txt" -fileprivate let urlMiscNonKanji: String = "./components/common/char-misc-nonkanji.txt" +private let urlKanjiCore: String = "./components/common/char-kanji-core.txt" +private let urlPunctuation: String = "./components/common/data-punctuations.txt" +private let urlMiscBPMF: String = "./components/common/char-misc-bpmf.txt" +private let urlMiscNonKanji: String = "./components/common/char-misc-nonkanji.txt" -fileprivate let urlOutputCHS: String = "./data-chs.txt" -fileprivate let urlOutputCHT: String = "./data-cht.txt" +private let urlOutputCHS: String = "./data-chs.txt" +private let urlOutputCHT: String = "./data-cht.txt" // MARK: - 載入詞組檔案且輸出數組 func rawDictForPhrases(isCHS: Bool) -> [Entry] { - var arrEntryRAW: [Entry] = [] - var strRAW: String = "" - let urlCustom: String = isCHS ? url_CHS_Custom : url_CHT_Custom - let urlMCBP: String = isCHS ? url_CHS_MCBP : url_CHT_MCBP - let urlMOE: String = isCHS ? url_CHS_MOE : url_CHT_MOE - let urlVCHEW: String = isCHS ? url_CHS_VCHEW : url_CHT_VCHEW - let i18n: String = isCHS ? "簡體中文" : "繁體中文" - // 讀取內容 - do { - strRAW += try String(contentsOfFile: urlCustom, encoding: .utf8) - strRAW += "\n" - strRAW += try String(contentsOfFile: urlMCBP, encoding: .utf8) - strRAW += "\n" - strRAW += try String(contentsOfFile: urlMOE, encoding: .utf8) - strRAW += "\n" - strRAW += try String(contentsOfFile: urlVCHEW, encoding: .utf8) - } - catch { - NSLog(" - Exception happened when reading raw phrases data.") - return [] - } - // 預處理格式 - strRAW = strRAW.replacingOccurrences(of: " #MACOS", with: "") // 去掉 macOS 標記 - // CJKWhiteSpace (\x{3000}) to ASCII Space - // NonBreakWhiteSpace (\x{A0}) to ASCII Space - // Tab to ASCII Space - // 統整連續空格為一個 ASCII 空格 - strRAW.regReplace(pattern: #"( +| +| +|\t+)+"#, replaceWith: " ") - strRAW.regReplace(pattern: #"(^ | $)"#, replaceWith: "") // 去除行尾行首空格 - strRAW.regReplace(pattern: #"(\f+|\r+|\n+)+"#, replaceWith: "\n") // CR & Form Feed to LF, 且去除重複行 - strRAW.regReplace(pattern: #"^(#.*|.*#WIN32.*)$"#, replaceWith: "") // 以#開頭的行都淨空+去掉所有 WIN32 特有的行 - // 正式整理格式,現在就開始去重複: - let arrData = Array(NSOrderedSet(array: strRAW.components(separatedBy: "\n")).array as! [String]) - for lineData in arrData { - // 第三欄開始是注音 - let arrLineData = lineData.components(separatedBy: " ") - var varLineDataProcessed: String = "" - var count = 0 - for currentCell in arrLineData { - count += 1 - if count < 3 { - varLineDataProcessed += currentCell + "\t" - } else if count < arrLineData.count { - varLineDataProcessed += currentCell + "-" - } else { - varLineDataProcessed += currentCell - } - } - // 然後直接乾脆就轉成 Entry 吧。 - let arrCells : [String] = varLineDataProcessed.components(separatedBy: "\t") - count = 0 // 不需要再定義,因為之前已經有定義過了。 - var phone = "" - var phrase = "" - var occurrence = 0 - for cell in arrCells { - count += 1 - switch count { - case 1: phrase = cell - case 3: phone = cell - case 2: occurrence = Int(cell) ?? 0 - default: break - } - } - if phrase != "" { // 廢掉空數據;之後無須再這樣處理。 - arrEntryRAW += [Entry.init(valPhone: phone, valPhrase: phrase, valWeight: 0.0, valCount: occurrence)] - } - } - NSLog(" - \(i18n): 成功生成詞語語料辭典(權重待計算)。") - return arrEntryRAW + var arrEntryRAW: [Entry] = [] + var strRAW: String = "" + let urlCustom: String = isCHS ? urlCHSforCustom : urlCHTforCustom + let urlMCBP: String = isCHS ? urlCHSforMCBP : urlCHTforMCBP + let urlMOE: String = isCHS ? urlCHSforMOE : urlCHTforMOE + let urlVCHEW: String = isCHS ? urlCHSforVCHEW : urlCHTforVCHEW + let i18n: String = isCHS ? "簡體中文" : "繁體中文" + // 讀取內容 + do { + strRAW += try String(contentsOfFile: urlCustom, encoding: .utf8) + strRAW += "\n" + strRAW += try String(contentsOfFile: urlMCBP, encoding: .utf8) + strRAW += "\n" + strRAW += try String(contentsOfFile: urlMOE, encoding: .utf8) + strRAW += "\n" + strRAW += try String(contentsOfFile: urlVCHEW, encoding: .utf8) + } catch { + NSLog(" - Exception happened when reading raw phrases data.") + return [] + } + // 預處理格式 + strRAW = strRAW.replacingOccurrences(of: " #MACOS", with: "") // 去掉 macOS 標記 + // CJKWhiteSpace (\x{3000}) to ASCII Space + // NonBreakWhiteSpace (\x{A0}) to ASCII Space + // Tab to ASCII Space + // 統整連續空格為一個 ASCII 空格 + strRAW.regReplace(pattern: #"( +| +| +|\t+)+"#, replaceWith: " ") + strRAW.regReplace(pattern: #"(^ | $)"#, replaceWith: "") // 去除行尾行首空格 + strRAW.regReplace(pattern: #"(\f+|\r+|\n+)+"#, replaceWith: "\n") // CR & Form Feed to LF, 且去除重複行 + strRAW.regReplace(pattern: #"^(#.*|.*#WIN32.*)$"#, replaceWith: "") // 以#開頭的行都淨空+去掉所有 WIN32 特有的行 + // 正式整理格式,現在就開始去重複: + let arrData = Array( + NSOrderedSet(array: strRAW.components(separatedBy: "\n")).array as! [String]) + for lineData in arrData { + // 第三欄開始是注音 + let arrLineData = lineData.components(separatedBy: " ") + var varLineDataProcessed: String = "" + var count = 0 + for currentCell in arrLineData { + count += 1 + if count < 3 { + varLineDataProcessed += currentCell + "\t" + } else if count < arrLineData.count { + varLineDataProcessed += currentCell + "-" + } else { + varLineDataProcessed += currentCell + } + } + // 然後直接乾脆就轉成 Entry 吧。 + let arrCells: [String] = varLineDataProcessed.components(separatedBy: "\t") + count = 0 // 不需要再定義,因為之前已經有定義過了。 + var phone = "" + var phrase = "" + var occurrence = 0 + for cell in arrCells { + count += 1 + switch count { + case 1: phrase = cell + case 3: phone = cell + case 2: occurrence = Int(cell) ?? 0 + default: break + } + } + if phrase != "" { // 廢掉空數據;之後無須再這樣處理。 + arrEntryRAW += [ + Entry.init( + valPhone: phone, valPhrase: phrase, valWeight: 0.0, + valCount: occurrence) + ] + } + } + NSLog(" - \(i18n): 成功生成詞語語料辭典(權重待計算)。") + return arrEntryRAW } // MARK: - 載入單字檔案且輸出數組 func rawDictForKanjis(isCHS: Bool) -> [Entry] { - var arrEntryRAW: [Entry] = [] - var strRAW: String = "" - let i18n: String = isCHS ? "簡體中文" : "繁體中文" - // 讀取內容 - do { - strRAW += try String(contentsOfFile: urlKanjiCore, encoding: .utf8) - } - catch { - NSLog(" - Exception happened when reading raw core kanji data.") - return [] - } - // 預處理格式 - strRAW = strRAW.replacingOccurrences(of: " #MACOS", with: "") // 去掉 macOS 標記 - // CJKWhiteSpace (\x{3000}) to ASCII Space - // NonBreakWhiteSpace (\x{A0}) to ASCII Space - // Tab to ASCII Space - // 統整連續空格為一個 ASCII 空格 - strRAW.regReplace(pattern: #"( +| +| +|\t+)+"#, replaceWith: " ") - strRAW.regReplace(pattern: #"(^ | $)"#, replaceWith: "") // 去除行尾行首空格 - strRAW.regReplace(pattern: #"(\f+|\r+|\n+)+"#, replaceWith: "\n") // CR & Form Feed to LF, 且去除重複行 - strRAW.regReplace(pattern: #"^(#.*|.*#WIN32.*)$"#, replaceWith: "") // 以#開頭的行都淨空+去掉所有 WIN32 特有的行 - // 正式整理格式,現在就開始去重複: - let arrData = Array(NSOrderedSet(array: strRAW.components(separatedBy: "\n")).array as! [String]) - var varLineData: String = "" - for lineData in arrData { - // 簡體中文的話,提取 1,2,4;繁體中文的話,提取 1,3,4。 - let varLineDataPre = lineData.components(separatedBy: " ").prefix(isCHS ? 2 : 1).joined(separator: "\t") - let varLineDataPost = lineData.components(separatedBy: " ").suffix(isCHS ? 1 : 2).joined(separator: "\t") - varLineData = varLineDataPre + "\t" + varLineDataPost - let arrLineData = varLineData.components(separatedBy: " ") - var varLineDataProcessed: String = "" - var count = 0 - for currentCell in arrLineData { - count += 1 - if count < 3 { - varLineDataProcessed += currentCell + "\t" - } else if count < arrLineData.count { - varLineDataProcessed += currentCell + "-" - } else { - varLineDataProcessed += currentCell - } - } - // 然後直接乾脆就轉成 Entry 吧。 - let arrCells : [String] = varLineDataProcessed.components(separatedBy: "\t") - count = 0 // 不需要再定義,因為之前已經有定義過了。 - var phone = "" - var phrase = "" - var occurrence = 0 - for cell in arrCells { - count += 1 - switch count { - case 1: phrase = cell - case 3: phone = cell - case 2: occurrence = Int(cell) ?? 0 - default: break - } - } - if phrase != "" { // 廢掉空數據;之後無須再這樣處理。 - arrEntryRAW += [Entry.init(valPhone: phone, valPhrase: phrase, valWeight: 0.0, valCount: occurrence)] - } - } - NSLog(" - \(i18n): 成功生成單字語料辭典(權重待計算)。") - return arrEntryRAW + var arrEntryRAW: [Entry] = [] + var strRAW: String = "" + let i18n: String = isCHS ? "簡體中文" : "繁體中文" + // 讀取內容 + do { + strRAW += try String(contentsOfFile: urlKanjiCore, encoding: .utf8) + } catch { + NSLog(" - Exception happened when reading raw core kanji data.") + return [] + } + // 預處理格式 + strRAW = strRAW.replacingOccurrences(of: " #MACOS", with: "") // 去掉 macOS 標記 + // CJKWhiteSpace (\x{3000}) to ASCII Space + // NonBreakWhiteSpace (\x{A0}) to ASCII Space + // Tab to ASCII Space + // 統整連續空格為一個 ASCII 空格 + strRAW.regReplace(pattern: #"( +| +| +|\t+)+"#, replaceWith: " ") + strRAW.regReplace(pattern: #"(^ | $)"#, replaceWith: "") // 去除行尾行首空格 + strRAW.regReplace(pattern: #"(\f+|\r+|\n+)+"#, replaceWith: "\n") // CR & Form Feed to LF, 且去除重複行 + strRAW.regReplace(pattern: #"^(#.*|.*#WIN32.*)$"#, replaceWith: "") // 以#開頭的行都淨空+去掉所有 WIN32 特有的行 + // 正式整理格式,現在就開始去重複: + let arrData = Array( + NSOrderedSet(array: strRAW.components(separatedBy: "\n")).array as! [String]) + var varLineData: String = "" + for lineData in arrData { + // 簡體中文的話,提取 1,2,4;繁體中文的話,提取 1,3,4。 + let varLineDataPre = lineData.components(separatedBy: " ").prefix(isCHS ? 2 : 1) + .joined( + separator: "\t") + let varLineDataPost = lineData.components(separatedBy: " ").suffix(isCHS ? 1 : 2) + .joined( + separator: "\t") + varLineData = varLineDataPre + "\t" + varLineDataPost + let arrLineData = varLineData.components(separatedBy: " ") + var varLineDataProcessed: String = "" + var count = 0 + for currentCell in arrLineData { + count += 1 + if count < 3 { + varLineDataProcessed += currentCell + "\t" + } else if count < arrLineData.count { + varLineDataProcessed += currentCell + "-" + } else { + varLineDataProcessed += currentCell + } + } + // 然後直接乾脆就轉成 Entry 吧。 + let arrCells: [String] = varLineDataProcessed.components(separatedBy: "\t") + count = 0 // 不需要再定義,因為之前已經有定義過了。 + var phone = "" + var phrase = "" + var occurrence = 0 + for cell in arrCells { + count += 1 + switch count { + case 1: phrase = cell + case 3: phone = cell + case 2: occurrence = Int(cell) ?? 0 + default: break + } + } + if phrase != "" { // 廢掉空數據;之後無須再這樣處理。 + arrEntryRAW += [ + Entry.init( + valPhone: phone, valPhrase: phrase, valWeight: 0.0, + valCount: occurrence) + ] + } + } + NSLog(" - \(i18n): 成功生成單字語料辭典(權重待計算)。") + return arrEntryRAW } // MARK: - 載入非漢字檔案且輸出數組 func rawDictForNonKanjis(isCHS: Bool) -> [Entry] { - var arrEntryRAW: [Entry] = [] - var strRAW: String = "" - let i18n: String = isCHS ? "簡體中文" : "繁體中文" - // 讀取內容 - do { - strRAW += try String(contentsOfFile: urlMiscBPMF, encoding: .utf8) - strRAW += "\n" - strRAW += try String(contentsOfFile: urlMiscNonKanji, encoding: .utf8) - } - catch { - NSLog(" - Exception happened when reading raw core kanji data.") - return [] - } - // 預處理格式 - strRAW = strRAW.replacingOccurrences(of: " #MACOS", with: "") // 去掉 macOS 標記 - // CJKWhiteSpace (\x{3000}) to ASCII Space - // NonBreakWhiteSpace (\x{A0}) to ASCII Space - // Tab to ASCII Space - // 統整連續空格為一個 ASCII 空格 - strRAW.regReplace(pattern: #"( +| +| +|\t+)+"#, replaceWith: " ") - strRAW.regReplace(pattern: #"(^ | $)"#, replaceWith: "") // 去除行尾行首空格 - strRAW.regReplace(pattern: #"(\f+|\r+|\n+)+"#, replaceWith: "\n") // CR & Form Feed to LF, 且去除重複行 - strRAW.regReplace(pattern: #"^(#.*|.*#WIN32.*)$"#, replaceWith: "") // 以#開頭的行都淨空+去掉所有 WIN32 特有的行 - // 正式整理格式,現在就開始去重複: - let arrData = Array(NSOrderedSet(array: strRAW.components(separatedBy: "\n")).array as! [String]) - var varLineData: String = "" - for lineData in arrData { - varLineData = lineData - // 先完成某兩步需要分行處理才能完成的格式整理。 - varLineData = varLineData.components(separatedBy: " ").prefix(3).joined(separator: "\t") // 提取前三欄的內容。 - let arrLineData = varLineData.components(separatedBy: " ") - var varLineDataProcessed: String = "" - var count = 0 - for currentCell in arrLineData { - count += 1 - if count < 3 { - varLineDataProcessed += currentCell + "\t" - } else if count < arrLineData.count { - varLineDataProcessed += currentCell + "-" - } else { - varLineDataProcessed += currentCell - } - } - // 然後直接乾脆就轉成 Entry 吧。 - let arrCells : [String] = varLineDataProcessed.components(separatedBy: "\t") - count = 0 // 不需要再定義,因為之前已經有定義過了。 - var phone = "" - var phrase = "" - var occurrence = 0 - for cell in arrCells { - count += 1 - switch count { - case 1: phrase = cell - case 3: phone = cell - case 2: occurrence = Int(cell) ?? 0 - default: break - } - } - if phrase != "" { // 廢掉空數據;之後無須再這樣處理。 - arrEntryRAW += [Entry.init(valPhone: phone, valPhrase: phrase, valWeight: 0.0, valCount: occurrence)] - } - } - NSLog(" - \(i18n): 成功生成非漢字語料辭典(權重待計算)。") - return arrEntryRAW + var arrEntryRAW: [Entry] = [] + var strRAW: String = "" + let i18n: String = isCHS ? "簡體中文" : "繁體中文" + // 讀取內容 + do { + strRAW += try String(contentsOfFile: urlMiscBPMF, encoding: .utf8) + strRAW += "\n" + strRAW += try String(contentsOfFile: urlMiscNonKanji, encoding: .utf8) + } catch { + NSLog(" - Exception happened when reading raw core kanji data.") + return [] + } + // 預處理格式 + strRAW = strRAW.replacingOccurrences(of: " #MACOS", with: "") // 去掉 macOS 標記 + // CJKWhiteSpace (\x{3000}) to ASCII Space + // NonBreakWhiteSpace (\x{A0}) to ASCII Space + // Tab to ASCII Space + // 統整連續空格為一個 ASCII 空格 + strRAW.regReplace(pattern: #"( +| +| +|\t+)+"#, replaceWith: " ") + strRAW.regReplace(pattern: #"(^ | $)"#, replaceWith: "") // 去除行尾行首空格 + strRAW.regReplace(pattern: #"(\f+|\r+|\n+)+"#, replaceWith: "\n") // CR & Form Feed to LF, 且去除重複行 + strRAW.regReplace(pattern: #"^(#.*|.*#WIN32.*)$"#, replaceWith: "") // 以#開頭的行都淨空+去掉所有 WIN32 特有的行 + // 正式整理格式,現在就開始去重複: + let arrData = Array( + NSOrderedSet(array: strRAW.components(separatedBy: "\n")).array as! [String]) + var varLineData: String = "" + for lineData in arrData { + varLineData = lineData + // 先完成某兩步需要分行處理才能完成的格式整理。 + varLineData = varLineData.components(separatedBy: " ").prefix(3).joined( + separator: "\t") // 提取前三欄的內容。 + let arrLineData = varLineData.components(separatedBy: " ") + var varLineDataProcessed: String = "" + var count = 0 + for currentCell in arrLineData { + count += 1 + if count < 3 { + varLineDataProcessed += currentCell + "\t" + } else if count < arrLineData.count { + varLineDataProcessed += currentCell + "-" + } else { + varLineDataProcessed += currentCell + } + } + // 然後直接乾脆就轉成 Entry 吧。 + let arrCells: [String] = varLineDataProcessed.components(separatedBy: "\t") + count = 0 // 不需要再定義,因為之前已經有定義過了。 + var phone = "" + var phrase = "" + var occurrence = 0 + for cell in arrCells { + count += 1 + switch count { + case 1: phrase = cell + case 3: phone = cell + case 2: occurrence = Int(cell) ?? 0 + default: break + } + } + if phrase != "" { // 廢掉空數據;之後無須再這樣處理。 + arrEntryRAW += [ + Entry.init( + valPhone: phone, valPhrase: phrase, valWeight: 0.0, + valCount: occurrence) + ] + } + } + NSLog(" - \(i18n): 成功生成非漢字語料辭典(權重待計算)。") + return arrEntryRAW } func weightAndSort(_ arrStructUncalculated: [Entry], isCHS: Bool) -> [Entry] { - let i18n: String = isCHS ? "簡體中文" : "繁體中文" - var arrStructCalculated: [Entry] = [] - let fscale: Float = 2.7 - var norm: Float = 0.0 - for entry in arrStructUncalculated { - if entry.valCount >= 0 { - norm += fscale**(Float(entry.valPhrase.count) / 3.0 - 1.0) * Float(entry.valCount) - } - } - // norm 計算完畢,開始將 norm 作為新的固定常數來為每個詞條記錄計算權重。 - // 將新酷音的詞語出現次數數據轉換成小麥引擎可讀的數據形式。 - // 對出現次數小於 1 的詞條,將 0 當成 0.5 來處理、以防止除零。 - for entry in arrStructUncalculated { - var weight: Float = 0 - switch entry.valCount { - case -2: // 拗音假名 - weight = -13 - case -1: // 單個假名 - weight = -13 - case 0: // 墊底低頻漢字與詞語 - weight = log10(fscale**(Float(entry.valPhrase.count) / 3.0 - 1.0) * 0.5 / norm) - default: - weight = log10(fscale**(Float(entry.valPhrase.count) / 3.0 - 1.0) * Float(entry.valCount) / norm) // Credit: MJHsieh. - } - let weightRounded: Float = weight.rounded(toPlaces: 3) // 為了節省生成的檔案體積,僅保留小數點後三位。 - arrStructCalculated += [Entry.init(valPhone: entry.valPhone, valPhrase: entry.valPhrase, valWeight: weightRounded, valCount: entry.valCount)] - } - NSLog(" - \(i18n): 成功計算權重。") - // ========================================== - // 接下來是排序,先按照注音遞減排序一遍、再按照權重遞減排序一遍。 - let arrStructSorted: [Entry] = arrStructCalculated.sorted(by: {(lhs, rhs) -> Bool in return (lhs.valPhone, rhs.valCount) < (rhs.valPhone, lhs.valCount)}) - NSLog(" - \(i18n): 排序整理完畢,準備編譯要寫入的檔案內容。") - return arrStructSorted + let i18n: String = isCHS ? "簡體中文" : "繁體中文" + var arrStructCalculated: [Entry] = [] + let fscale: Float = 2.7 + var norm: Float = 0.0 + for entry in arrStructUncalculated { + if entry.valCount >= 0 { + norm += fscale ** (Float(entry.valPhrase.count) / 3.0 - 1.0) + * Float(entry.valCount) + } + } + // norm 計算完畢,開始將 norm 作為新的固定常數來為每個詞條記錄計算權重。 + // 將新酷音的詞語出現次數數據轉換成小麥引擎可讀的數據形式。 + // 對出現次數小於 1 的詞條,將 0 當成 0.5 來處理、以防止除零。 + for entry in arrStructUncalculated { + var weight: Float = 0 + switch entry.valCount { + case -2: // 拗音假名 + weight = -13 + case -1: // 單個假名 + weight = -13 + case 0: // 墊底低頻漢字與詞語 + weight = log10( + fscale ** (Float(entry.valPhrase.count) / 3.0 - 1.0) * 0.5 / norm) + default: + weight = log10( + fscale ** (Float(entry.valPhrase.count) / 3.0 - 1.0) + * Float(entry.valCount) / norm) // Credit: MJHsieh. + } + let weightRounded: Float = weight.rounded(toPlaces: 3) // 為了節省生成的檔案體積,僅保留小數點後三位。 + arrStructCalculated += [ + Entry.init( + valPhone: entry.valPhone, valPhrase: entry.valPhrase, valWeight: weightRounded, + valCount: entry.valCount) + ] + } + NSLog(" - \(i18n): 成功計算權重。") + // ========================================== + // 接下來是排序,先按照注音遞減排序一遍、再按照權重遞減排序一遍。 + let arrStructSorted: [Entry] = arrStructCalculated.sorted(by: { (lhs, rhs) -> Bool in + return (lhs.valPhone, rhs.valCount) < (rhs.valPhone, lhs.valCount) + }) + NSLog(" - \(i18n): 排序整理完畢,準備編譯要寫入的檔案內容。") + return arrStructSorted } func fileOutput(isCHS: Bool) { - let i18n: String = isCHS ? "簡體中文" : "繁體中文" - let pathOutput = urlCurrentFolder.appendingPathComponent(isCHS ? urlOutputCHS : urlOutputCHT) - var strPrintLine = "" - // 讀取標點內容 - do { - strPrintLine += try String(contentsOfFile: urlPunctuation, encoding: .utf8) - } - catch { - NSLog(" - \(i18n): Exception happened when reading raw punctuation data.") - } - NSLog(" - \(i18n): 成功插入標點符號與西文字母數據。") - // 統合辭典內容 - var arrStructUnified: [Entry] = [] - arrStructUnified += rawDictForKanjis(isCHS: isCHS) - arrStructUnified += rawDictForNonKanjis(isCHS: isCHS) - arrStructUnified += rawDictForPhrases(isCHS: isCHS) - // 計算權重且排序 - arrStructUnified = weightAndSort(arrStructUnified, isCHS: isCHS) - - for entry in arrStructUnified { - strPrintLine += entry.valPhone + " " + entry.valPhrase + " " + String(entry.valWeight) + "\n" - } - NSLog(" - \(i18n): 要寫入檔案的內容編譯完畢。") - do { - try strPrintLine.write(to: pathOutput, atomically: false, encoding: .utf8) - } - catch { - NSLog(" - \(i18n): Error on writing strings to file: \(error)") - } - NSLog(" - \(i18n): 寫入完成。") + let i18n: String = isCHS ? "簡體中文" : "繁體中文" + let pathOutput = urlCurrentFolder.appendingPathComponent( + isCHS ? urlOutputCHS : urlOutputCHT) + var strPrintLine = "" + // 讀取標點內容 + do { + strPrintLine += try String(contentsOfFile: urlPunctuation, encoding: .utf8) + } catch { + NSLog(" - \(i18n): Exception happened when reading raw punctuation data.") + } + NSLog(" - \(i18n): 成功插入標點符號與西文字母數據。") + // 統合辭典內容 + var arrStructUnified: [Entry] = [] + arrStructUnified += rawDictForKanjis(isCHS: isCHS) + arrStructUnified += rawDictForNonKanjis(isCHS: isCHS) + arrStructUnified += rawDictForPhrases(isCHS: isCHS) + // 計算權重且排序 + arrStructUnified = weightAndSort(arrStructUnified, isCHS: isCHS) + + for entry in arrStructUnified { + strPrintLine += + entry.valPhone + " " + entry.valPhrase + " " + String(entry.valWeight) + + "\n" + } + NSLog(" - \(i18n): 要寫入檔案的內容編譯完畢。") + do { + try strPrintLine.write(to: pathOutput, atomically: false, encoding: .utf8) + } catch { + NSLog(" - \(i18n): Error on writing strings to file: \(error)") + } + NSLog(" - \(i18n): 寫入完成。") } // MARK: - 主执行绪 func main() { - NSLog("// 準備編譯繁體中文核心語料檔案。") - fileOutput(isCHS: false) - NSLog("// 準備編譯簡體中文核心語料檔案。") - fileOutput(isCHS: true) + NSLog("// 準備編譯繁體中文核心語料檔案。") + fileOutput(isCHS: false) + NSLog("// 準備編譯簡體中文核心語料檔案。") + fileOutput(isCHS: true) } main() diff --git a/Installer/AppDelegate.swift b/Installer/AppDelegate.swift index b9d212c6..996d9688 100644 --- a/Installer/AppDelegate.swift +++ b/Installer/AppDelegate.swift @@ -1,20 +1,27 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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 @@ -23,9 +30,11 @@ private let kTargetBin = "vChewing" private let kTargetType = "app" private let kTargetBundle = "vChewing.app" -private let urlDestinationPartial = FileManager.default.urls(for: .inputMethodsDirectory, in: .userDomainMask)[0] +private let urlDestinationPartial = FileManager.default.urls( + for: .inputMethodsDirectory, in: .userDomainMask)[0] private let urlTargetPartial = urlDestinationPartial.appendingPathComponent(kTargetBundle) -private let urlTargetFullBinPartial = urlTargetPartial.appendingPathComponent("Contents/MacOS/").appendingPathComponent(kTargetBin) +private let urlTargetFullBinPartial = urlTargetPartial.appendingPathComponent("Contents/MacOS/") + .appendingPathComponent(kTargetBin) private let kDestinationPartial = urlDestinationPartial.path private let kTargetPartialPath = urlTargetPartial.path @@ -35,251 +44,300 @@ private let kTranslocationRemovalTickInterval: TimeInterval = 0.5 private let kTranslocationRemovalDeadline: TimeInterval = 60.0 @NSApplicationMain -@objc (AppDelegate) +@objc(AppDelegate) class AppDelegate: NSWindowController, NSApplicationDelegate { - @IBOutlet weak private var installButton: NSButton! - @IBOutlet weak private var cancelButton: NSButton! - @IBOutlet weak private var progressSheet: NSWindow! - @IBOutlet weak private var progressIndicator: NSProgressIndicator! - @IBOutlet weak private var appVersionLabel: NSTextField! - @IBOutlet weak private var appCopyrightLabel: NSTextField! - @IBOutlet private var appEULAContent: NSTextView! - - private var archiveUtil: ArchiveUtil? - private var installingVersion = "" - private var upgrading = false - private var translocationRemovalStartTime: Date? - private var currentVersionNumber: Int = 0 + @IBOutlet weak private var installButton: NSButton! + @IBOutlet weak private var cancelButton: NSButton! + @IBOutlet weak private var progressSheet: NSWindow! + @IBOutlet weak private var progressIndicator: NSProgressIndicator! + @IBOutlet weak private var appVersionLabel: NSTextField! + @IBOutlet weak private var appCopyrightLabel: NSTextField! + @IBOutlet private var appEULAContent: NSTextView! - func runAlertPanel(title: String, message: String, buttonTitle: String) { - let alert = NSAlert() - alert.alertStyle = .informational - alert.messageText = title - alert.informativeText = message - alert.addButton(withTitle: buttonTitle) - alert.runModal() - } + private var archiveUtil: ArchiveUtil? + private var installingVersion = "" + private var upgrading = false + private var translocationRemovalStartTime: Date? + private var currentVersionNumber: Int = 0 - func applicationDidFinishLaunching(_ notification: Notification) { - guard let installingVersion = Bundle.main.infoDictionary?[kCFBundleVersionKey as String] as? String, - let versionString = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String else { - return - } - self.installingVersion = installingVersion - self.archiveUtil = ArchiveUtil(appName: kTargetBin, targetAppBundleName: kTargetBundle) - _ = archiveUtil?.validateIfNotarizedArchiveExists() + func runAlertPanel(title: String, message: String, buttonTitle: String) { + let alert = NSAlert() + alert.alertStyle = .informational + alert.messageText = title + alert.informativeText = message + alert.addButton(withTitle: buttonTitle) + alert.runModal() + } - cancelButton.nextKeyView = installButton - installButton.nextKeyView = cancelButton - if let cell = installButton.cell as? NSButtonCell { - window?.defaultButtonCell = cell - } - - if let copyrightLabel = Bundle.main.localizedInfoDictionary?["NSHumanReadableCopyright"] as? String { - appCopyrightLabel.stringValue = copyrightLabel - } - if let eulaContent = Bundle.main.localizedInfoDictionary?["CFEULAContent"] as? String { - appEULAContent.string = eulaContent - } - appVersionLabel.stringValue = String(format: "%@ Build %@", versionString, installingVersion) + func applicationDidFinishLaunching(_ notification: Notification) { + guard + let installingVersion = Bundle.main.infoDictionary?[kCFBundleVersionKey as String] + as? String, + let versionString = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String + else { + return + } + self.installingVersion = installingVersion + self.archiveUtil = ArchiveUtil(appName: kTargetBin, targetAppBundleName: kTargetBundle) + _ = archiveUtil?.validateIfNotarizedArchiveExists() - window?.title = String(format: NSLocalizedString("%@ (for version %@, r%@)", comment: ""), window?.title ?? "", versionString, installingVersion) - window?.standardWindowButton(.closeButton)?.isHidden = true - window?.standardWindowButton(.miniaturizeButton)?.isHidden = true - window?.standardWindowButton(.zoomButton)?.isHidden = true + cancelButton.nextKeyView = installButton + installButton.nextKeyView = cancelButton + if let cell = installButton.cell as? NSButtonCell { + window?.defaultButtonCell = cell + } - if FileManager.default.fileExists(atPath: (kTargetPartialPath as NSString).expandingTildeInPath) { - let currentBundle = Bundle(path: (kTargetPartialPath as NSString).expandingTildeInPath) - let shortVersion = currentBundle?.infoDictionary?["CFBundleShortVersionString"] as? String - let currentVersion = currentBundle?.infoDictionary?[kCFBundleVersionKey as String] as? String - currentVersionNumber = (currentVersion as NSString?)?.integerValue ?? 0 - if shortVersion != nil, let currentVersion = currentVersion, currentVersion.compare(installingVersion, options: .numeric) == .orderedAscending { - upgrading = true - } - } + if let copyrightLabel = Bundle.main.localizedInfoDictionary?["NSHumanReadableCopyright"] + as? String + { + appCopyrightLabel.stringValue = copyrightLabel + } + if let eulaContent = Bundle.main.localizedInfoDictionary?["CFEULAContent"] as? String { + appEULAContent.string = eulaContent + } + appVersionLabel.stringValue = String( + format: "%@ Build %@", versionString, installingVersion) - if upgrading { - installButton.title = NSLocalizedString("Upgrade", comment: "") - } + window?.title = String( + format: NSLocalizedString("%@ (for version %@, r%@)", comment: ""), window?.title ?? "", + versionString, installingVersion) + window?.standardWindowButton(.closeButton)?.isHidden = true + window?.standardWindowButton(.miniaturizeButton)?.isHidden = true + window?.standardWindowButton(.zoomButton)?.isHidden = true - window?.center() - window?.orderFront(self) - NSApp.activate(ignoringOtherApps: true) - } + if FileManager.default.fileExists( + atPath: (kTargetPartialPath as NSString).expandingTildeInPath) + { + let currentBundle = Bundle(path: (kTargetPartialPath as NSString).expandingTildeInPath) + let shortVersion = + currentBundle?.infoDictionary?["CFBundleShortVersionString"] as? String + let currentVersion = + currentBundle?.infoDictionary?[kCFBundleVersionKey as String] as? String + currentVersionNumber = (currentVersion as NSString?)?.integerValue ?? 0 + if shortVersion != nil, let currentVersion = currentVersion, + currentVersion.compare(installingVersion, options: .numeric) == .orderedAscending + { + upgrading = true + } + } - @IBAction func agreeAndInstallAction(_ sender: AnyObject) { - cancelButton.isEnabled = false - installButton.isEnabled = false - removeThenInstallInputMethod() - } + if upgrading { + installButton.title = NSLocalizedString("Upgrade", comment: "") + } - @objc func timerTick(_ timer: Timer) { - let elapsed = Date().timeIntervalSince(translocationRemovalStartTime ?? Date()) - if elapsed >= kTranslocationRemovalDeadline { - timer.invalidate() - window?.endSheet(progressSheet, returnCode: .cancel) - } else if appBundleChronoshiftedToARandomizedPath(kTargetPartialPath) == false { - progressIndicator.doubleValue = 1.0 - timer.invalidate() - window?.endSheet(progressSheet, returnCode: .continue) - } - } + window?.center() + window?.orderFront(self) + NSApp.activate(ignoringOtherApps: true) + } - func removeThenInstallInputMethod() { - if FileManager.default.fileExists(atPath: (kTargetPartialPath as NSString).expandingTildeInPath) == false { - self.installInputMethod(previousExists: false, previousVersionNotFullyDeactivatedWarning: false) - return - } + @IBAction func agreeAndInstallAction(_ sender: AnyObject) { + cancelButton.isEnabled = false + installButton.isEnabled = false + removeThenInstallInputMethod() + } - let shouldWaitForTranslocationRemoval = appBundleChronoshiftedToARandomizedPath(kTargetPartialPath) && (window?.responds(to: #selector(NSWindow.beginSheet(_:completionHandler:))) ?? false) + @objc func timerTick(_ timer: Timer) { + let elapsed = Date().timeIntervalSince(translocationRemovalStartTime ?? Date()) + if elapsed >= kTranslocationRemovalDeadline { + timer.invalidate() + window?.endSheet(progressSheet, returnCode: .cancel) + } else if appBundleChronoshiftedToARandomizedPath(kTargetPartialPath) == false { + progressIndicator.doubleValue = 1.0 + timer.invalidate() + window?.endSheet(progressSheet, returnCode: .continue) + } + } - // 將既存輸入法扔到垃圾桶內 - do { - let sourceDir = (kDestinationPartial as NSString).expandingTildeInPath - let fileManager = FileManager.default - let fileURLString = String(format: "%@/%@", sourceDir, kTargetBundle) - let fileURL = URL(fileURLWithPath: fileURLString) - - // 檢查檔案是否存在 - if fileManager.fileExists(atPath: fileURLString) { - // 塞入垃圾桶 - try fileManager.trashItem(at: fileURL, resultingItemURL: nil) - } else { - NSLog("File does not exist") - } - - } - catch let error as NSError { - NSLog("An error took place: \(error)") - } + func removeThenInstallInputMethod() { + if FileManager.default.fileExists( + atPath: (kTargetPartialPath as NSString).expandingTildeInPath) + == false + { + self.installInputMethod( + previousExists: false, previousVersionNotFullyDeactivatedWarning: false) + return + } - let killTask = Process() - killTask.launchPath = "/usr/bin/killall" - killTask.arguments = ["-9", kTargetBin] - killTask.launch() - killTask.waitUntilExit() + let shouldWaitForTranslocationRemoval = + appBundleChronoshiftedToARandomizedPath(kTargetPartialPath) + && (window?.responds(to: #selector(NSWindow.beginSheet(_:completionHandler:))) ?? false) - if shouldWaitForTranslocationRemoval { - progressIndicator.startAnimation(self) - window?.beginSheet(progressSheet) { returnCode in - DispatchQueue.main.async { - if returnCode == .continue { - self.installInputMethod(previousExists: true, previousVersionNotFullyDeactivatedWarning: false) - } else { - self.installInputMethod(previousExists: true, previousVersionNotFullyDeactivatedWarning: true) - } - } - } + // 將既存輸入法扔到垃圾桶內 + do { + let sourceDir = (kDestinationPartial as NSString).expandingTildeInPath + let fileManager = FileManager.default + let fileURLString = String(format: "%@/%@", sourceDir, kTargetBundle) + let fileURL = URL(fileURLWithPath: fileURLString) - translocationRemovalStartTime = Date() - Timer.scheduledTimer(timeInterval: kTranslocationRemovalTickInterval, target: self, selector: #selector(timerTick(_:)), userInfo: nil, repeats: true) + // 檢查檔案是否存在 + if fileManager.fileExists(atPath: fileURLString) { + // 塞入垃圾桶 + try fileManager.trashItem(at: fileURL, resultingItemURL: nil) + } else { + NSLog("File does not exist") + } + + } catch let error as NSError { + NSLog("An error took place: \(error)") + } + + let killTask = Process() + killTask.launchPath = "/usr/bin/killall" + killTask.arguments = ["-9", kTargetBin] + killTask.launch() + killTask.waitUntilExit() + + if shouldWaitForTranslocationRemoval { + progressIndicator.startAnimation(self) + window?.beginSheet(progressSheet) { returnCode in + DispatchQueue.main.async { + if returnCode == .continue { + self.installInputMethod( + previousExists: true, + previousVersionNotFullyDeactivatedWarning: false) + } else { + self.installInputMethod( + previousExists: true, + previousVersionNotFullyDeactivatedWarning: true) + } + } + } + + translocationRemovalStartTime = Date() + Timer.scheduledTimer( + timeInterval: kTranslocationRemovalTickInterval, target: self, + selector: #selector(timerTick(_:)), userInfo: nil, repeats: true) } else { - self.installInputMethod(previousExists: false, previousVersionNotFullyDeactivatedWarning: false) - } - } + self.installInputMethod( + previousExists: false, previousVersionNotFullyDeactivatedWarning: false) + } + } - func installInputMethod(previousExists: Bool, previousVersionNotFullyDeactivatedWarning warning: Bool) { - guard let targetBundle = archiveUtil?.unzipNotarizedArchive() ?? Bundle.main.path(forResource: kTargetBin, ofType: kTargetType) else { - return - } - let cpTask = Process() - cpTask.launchPath = "/bin/cp" - cpTask.arguments = ["-R", targetBundle, (kDestinationPartial as NSString).expandingTildeInPath] - cpTask.launch() - cpTask.waitUntilExit() + func installInputMethod( + previousExists: Bool, previousVersionNotFullyDeactivatedWarning warning: Bool + ) { + guard + let targetBundle = archiveUtil?.unzipNotarizedArchive() + ?? Bundle.main.path(forResource: kTargetBin, ofType: kTargetType) + else { + return + } + let cpTask = Process() + cpTask.launchPath = "/bin/cp" + cpTask.arguments = [ + "-R", targetBundle, (kDestinationPartial as NSString).expandingTildeInPath, + ] + cpTask.launch() + cpTask.waitUntilExit() - if cpTask.terminationStatus != 0 { - runAlertPanel(title: NSLocalizedString("Install Failed", comment: ""), - message: NSLocalizedString("Cannot copy the file to the destination.", comment: ""), - buttonTitle: NSLocalizedString("Cancel", comment: "")) - endAppWithDelay() - } + if cpTask.terminationStatus != 0 { + runAlertPanel( + title: NSLocalizedString("Install Failed", comment: ""), + message: NSLocalizedString("Cannot copy the file to the destination.", comment: ""), + buttonTitle: NSLocalizedString("Cancel", comment: "")) + endAppWithDelay() + } - guard let imeBundle = Bundle(path: (kTargetPartialPath as NSString).expandingTildeInPath), - let imeIdentifier = imeBundle.bundleIdentifier - else { - endAppWithDelay() - return - } + guard let imeBundle = Bundle(path: (kTargetPartialPath as NSString).expandingTildeInPath), + let imeIdentifier = imeBundle.bundleIdentifier + else { + endAppWithDelay() + return + } - let imeBundleURL = imeBundle.bundleURL - var inputSource = InputSourceHelper.inputSource(for: imeIdentifier) + let imeBundleURL = imeBundle.bundleURL + var inputSource = InputSourceHelper.inputSource(for: imeIdentifier) - if inputSource == nil { - NSLog("Registering input source \(imeIdentifier) at \(imeBundleURL.absoluteString)."); - let status = InputSourceHelper.registerTnputSource(at: imeBundleURL) - if !status { - let message = String(format: NSLocalizedString("Cannot find input source %@ after registration.", comment: ""), imeIdentifier) - runAlertPanel(title: NSLocalizedString("Fatal Error", comment: ""), message: message, buttonTitle: NSLocalizedString("Abort", comment: "")) - endAppWithDelay() - return - } + if inputSource == nil { + NSLog("Registering input source \(imeIdentifier) at \(imeBundleURL.absoluteString).") + let status = InputSourceHelper.registerTnputSource(at: imeBundleURL) + if !status { + let message = String( + format: NSLocalizedString( + "Cannot find input source %@ after registration.", comment: ""), + imeIdentifier) + runAlertPanel( + title: NSLocalizedString("Fatal Error", comment: ""), message: message, + buttonTitle: NSLocalizedString("Abort", comment: "")) + endAppWithDelay() + return + } - inputSource = InputSourceHelper.inputSource(for: imeIdentifier) - if inputSource == nil { - let message = String(format: NSLocalizedString("Cannot find input source %@ after registration.", comment: ""), imeIdentifier) - runAlertPanel(title: NSLocalizedString("Fatal Error", comment: ""), message: message, buttonTitle: NSLocalizedString("Abort", comment: "")) - } - } + inputSource = InputSourceHelper.inputSource(for: imeIdentifier) + if inputSource == nil { + let message = String( + format: NSLocalizedString( + "Cannot find input source %@ after registration.", comment: ""), + imeIdentifier) + runAlertPanel( + title: NSLocalizedString("Fatal Error", comment: ""), message: message, + buttonTitle: NSLocalizedString("Abort", comment: "")) + } + } - var isMacOS12OrAbove = false - if #available(macOS 12.0, *) { - NSLog("macOS 12 or later detected."); - isMacOS12OrAbove = true - } else { - NSLog("Installer runs with the pre-macOS 12 flow."); - } + var isMacOS12OrAbove = false + if #available(macOS 12.0, *) { + NSLog("macOS 12 or later detected.") + isMacOS12OrAbove = true + } else { + NSLog("Installer runs with the pre-macOS 12 flow.") + } - // If the IME is not enabled, enable it. Also, unconditionally enable it on macOS 12.0+, - // as the kTISPropertyInputSourceIsEnabled can still be true even if the IME is *not* - // enabled in the user's current set of IMEs (which means the IME does not show up in - // the user's input menu). + // If the IME is not enabled, enable it. Also, unconditionally enable it on macOS 12.0+, + // as the kTISPropertyInputSourceIsEnabled can still be true even if the IME is *not* + // enabled in the user's current set of IMEs (which means the IME does not show up in + // the user's input menu). - var mainInputSourceEnabled = InputSourceHelper.inputSourceEnabled(for: inputSource!) - if !mainInputSourceEnabled || isMacOS12OrAbove { - mainInputSourceEnabled = InputSourceHelper.enable(inputSource: inputSource!) - if (mainInputSourceEnabled) { - NSLog("Input method enabled: \(imeIdentifier)"); - } else { - NSLog("Failed to enable input method: \(imeIdentifier)"); - } - } - - // Alert Panel - let ntfPostInstall = NSAlert() - if warning { - ntfPostInstall.messageText = NSLocalizedString("Attention", comment: "") - ntfPostInstall.informativeText = NSLocalizedString("vChewing is upgraded, but please log out or reboot for the new version to be fully functional.", comment: "") - ntfPostInstall.addButton(withTitle: NSLocalizedString("OK", comment: "")) - } else { - if !mainInputSourceEnabled && !isMacOS12OrAbove { - ntfPostInstall.messageText = NSLocalizedString("Warning", comment: "") - ntfPostInstall.informativeText = NSLocalizedString("Input method may not be fully enabled. Please enable it through System Preferences > Keyboard > Input Sources.", comment: "") - ntfPostInstall.addButton(withTitle: NSLocalizedString("Continue", comment: "")) - } else { - ntfPostInstall.messageText = NSLocalizedString("Installation Successful", comment: "") - ntfPostInstall.informativeText = NSLocalizedString("vChewing is ready to use.", comment: "") - ntfPostInstall.addButton(withTitle: NSLocalizedString("OK", comment: "")) - } - } - ntfPostInstall.beginSheetModal(for: window!) { response in - self.endAppWithDelay() - } - } + var mainInputSourceEnabled = InputSourceHelper.inputSourceEnabled(for: inputSource!) + if !mainInputSourceEnabled || isMacOS12OrAbove { + mainInputSourceEnabled = InputSourceHelper.enable(inputSource: inputSource!) + if mainInputSourceEnabled { + NSLog("Input method enabled: \(imeIdentifier)") + } else { + NSLog("Failed to enable input method: \(imeIdentifier)") + } + } - func endAppWithDelay() { - DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) { - NSApp.terminate(self) - } - } + // Alert Panel + let ntfPostInstall = NSAlert() + if warning { + ntfPostInstall.messageText = NSLocalizedString("Attention", comment: "") + ntfPostInstall.informativeText = NSLocalizedString( + "vChewing is upgraded, but please log out or reboot for the new version to be fully functional.", + comment: "") + ntfPostInstall.addButton(withTitle: NSLocalizedString("OK", comment: "")) + } else { + if !mainInputSourceEnabled && !isMacOS12OrAbove { + ntfPostInstall.messageText = NSLocalizedString("Warning", comment: "") + ntfPostInstall.informativeText = NSLocalizedString( + "Input method may not be fully enabled. Please enable it through System Preferences > Keyboard > Input Sources.", + comment: "") + ntfPostInstall.addButton(withTitle: NSLocalizedString("Continue", comment: "")) + } else { + ntfPostInstall.messageText = NSLocalizedString( + "Installation Successful", comment: "") + ntfPostInstall.informativeText = NSLocalizedString( + "vChewing is ready to use.", comment: "") + ntfPostInstall.addButton(withTitle: NSLocalizedString("OK", comment: "")) + } + } + ntfPostInstall.beginSheetModal(for: window!) { response in + self.endAppWithDelay() + } + } - @IBAction func cancelAction(_ sender: AnyObject) { - NSApp.terminate(self) - } + func endAppWithDelay() { + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) { + NSApp.terminate(self) + } + } - func windowWillClose(_ Notification: Notification) { - NSApp.terminate(self) - } + @IBAction func cancelAction(_ sender: AnyObject) { + NSApp.terminate(self) + } + func windowWillClose(_ notification: Notification) { + NSApp.terminate(self) + } } diff --git a/Installer/ArchiveUtil.swift b/Installer/ArchiveUtil.swift index a0d25027..737fd959 100644 --- a/Installer/ArchiveUtil.swift +++ b/Installer/ArchiveUtil.swift @@ -1,117 +1,134 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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 struct ArchiveUtil { - var appName: String - var targetAppBundleName: String + var appName: String + var targetAppBundleName: String - init(appName: String, targetAppBundleName: String) { - self.appName = appName - self.targetAppBundleName = targetAppBundleName - } + init(appName: String, targetAppBundleName: String) { + self.appName = appName + self.targetAppBundleName = targetAppBundleName + } - // Returns YES if (1) a zip file under - // Resources/NotarizedArchives/$_appName-$bundleVersion.zip exists, and (2) if - // Resources/$_invalidAppBundleName does not exist. - func validateIfNotarizedArchiveExists() -> Bool { - guard let resourePath = Bundle.main.resourcePath, - let notarizedArchivesPath = notarizedArchivesPath, - let notarizedArchive = notarizedArchive, - let notarizedArchivesContent: [String] = try? FileManager.default.subpathsOfDirectory(atPath: notarizedArchivesPath) - else { - return false - } + // Returns YES if (1) a zip file under + // Resources/NotarizedArchives/$_appName-$bundleVersion.zip exists, and (2) if + // Resources/$_invalidAppBundleName does not exist. + func validateIfNotarizedArchiveExists() -> Bool { + guard let resourePath = Bundle.main.resourcePath, + let notarizedArchivesPath = notarizedArchivesPath, + let notarizedArchive = notarizedArchive, + let notarizedArchivesContent: [String] = try? FileManager.default.subpathsOfDirectory( + atPath: notarizedArchivesPath) + else { + return false + } - let devModeAppBundlePath = (resourePath as NSString).appendingPathComponent(targetAppBundleName) - let count = notarizedArchivesContent.count - let notarizedArchiveExists = FileManager.default.fileExists(atPath: notarizedArchive) - let devModeAppBundleExists = FileManager.default.fileExists(atPath: devModeAppBundlePath) + let devModeAppBundlePath = (resourePath as NSString).appendingPathComponent(targetAppBundleName) + let count = notarizedArchivesContent.count + let notarizedArchiveExists = FileManager.default.fileExists(atPath: notarizedArchive) + let devModeAppBundleExists = FileManager.default.fileExists(atPath: devModeAppBundlePath) - if count > 0 { - if count != 1 || !notarizedArchiveExists || devModeAppBundleExists { - let alert = NSAlert() - alert.alertStyle = .informational - alert.messageText = "Internal Error" - alert.informativeText = "devMode installer, expected archive name: \(notarizedArchive), " + - "archive exists: \(notarizedArchiveExists), devMode app bundle exists: \(devModeAppBundleExists)" - alert.addButton(withTitle: "Terminate") - alert.runModal() - NSApp.terminate(nil) - } else { - return true - } - } + if count > 0 { + if count != 1 || !notarizedArchiveExists || devModeAppBundleExists { + let alert = NSAlert() + alert.alertStyle = .informational + alert.messageText = "Internal Error" + alert.informativeText = + "devMode installer, expected archive name: \(notarizedArchive), " + + "archive exists: \(notarizedArchiveExists), devMode app bundle exists: \(devModeAppBundleExists)" + alert.addButton(withTitle: "Terminate") + alert.runModal() + NSApp.terminate(nil) + } else { + return true + } + } - if !devModeAppBundleExists { - let alert = NSAlert() - alert.alertStyle = .informational - alert.messageText = "Internal Error" - alert.informativeText = "Dev target bundle does not exist: \(devModeAppBundlePath)" - alert.addButton(withTitle: "Terminate") - alert.runModal() - NSApp.terminate(nil) - } + if !devModeAppBundleExists { + let alert = NSAlert() + alert.alertStyle = .informational + alert.messageText = "Internal Error" + alert.informativeText = "Dev target bundle does not exist: \(devModeAppBundlePath)" + alert.addButton(withTitle: "Terminate") + alert.runModal() + NSApp.terminate(nil) + } - return false - } + return false + } - func unzipNotarizedArchive() -> String? { - if !self.validateIfNotarizedArchiveExists() { - return nil - } - guard let notarizedArchive = notarizedArchive, - let resourcePath = Bundle.main.resourcePath else { - return nil - } - let tempFilePath = (NSTemporaryDirectory() as NSString).appendingPathComponent(UUID().uuidString) - let arguments: [String] = [notarizedArchive, "-d", tempFilePath] - let unzipTask = Process() - unzipTask.launchPath = "/usr/bin/unzip" - unzipTask.currentDirectoryPath = resourcePath - unzipTask.arguments = arguments - unzipTask.launch() - unzipTask.waitUntilExit() + func unzipNotarizedArchive() -> String? { + if !self.validateIfNotarizedArchiveExists() { + return nil + } + guard let notarizedArchive = notarizedArchive, + let resourcePath = Bundle.main.resourcePath + else { + return nil + } + let tempFilePath = (NSTemporaryDirectory() as NSString).appendingPathComponent( + UUID().uuidString) + let arguments: [String] = [notarizedArchive, "-d", tempFilePath] + let unzipTask = Process() + unzipTask.launchPath = "/usr/bin/unzip" + unzipTask.currentDirectoryPath = resourcePath + unzipTask.arguments = arguments + unzipTask.launch() + unzipTask.waitUntilExit() - assert(unzipTask.terminationStatus == 0, "Must successfully unzipped") - let result = (tempFilePath as NSString).appendingPathComponent(targetAppBundleName) - assert(FileManager.default.fileExists(atPath: result), "App bundle must be unzipped at \(result).") - return result - } + assert(unzipTask.terminationStatus == 0, "Must successfully unzipped") + let result = (tempFilePath as NSString).appendingPathComponent(targetAppBundleName) + assert( + FileManager.default.fileExists(atPath: result), + "App bundle must be unzipped at \(result).") + return result + } - private var notarizedArchivesPath: String? { - guard let resourePath = Bundle.main.resourcePath else { - return nil - } - let notarizedArchivesPath = (resourePath as NSString).appendingPathComponent("NotarizedArchives") - return notarizedArchivesPath - } + private var notarizedArchivesPath: String? { + guard let resourePath = Bundle.main.resourcePath else { + return nil + } + let notarizedArchivesPath = (resourePath as NSString).appendingPathComponent( + "NotarizedArchives") + return notarizedArchivesPath + } - private var notarizedArchive: String? { - guard let notarizedArchivesPath = notarizedArchivesPath, - let bundleVersion = Bundle.main.infoDictionary?[kCFBundleVersionKey as String] as? String else { - return nil - } - let notarizedArchiveBasename = "\(appName)-r\(bundleVersion).zip" - let notarizedArchive = (notarizedArchivesPath as NSString).appendingPathComponent(notarizedArchiveBasename) - return notarizedArchive - } + private var notarizedArchive: String? { + guard let notarizedArchivesPath = notarizedArchivesPath, + let bundleVersion = Bundle.main.infoDictionary?[kCFBundleVersionKey as String] + as? String + else { + return nil + } + let notarizedArchiveBasename = "\(appName)-r\(bundleVersion).zip" + let notarizedArchive = (notarizedArchivesPath as NSString).appendingPathComponent( + notarizedArchiveBasename) + return notarizedArchive + } } diff --git a/Source/3rdParty/OpenCCBridge/OpenCCBridge.swift b/Source/3rdParty/OpenCCBridge/OpenCCBridge.swift index f1f23af8..9d8d209b 100644 --- a/Source/3rdParty/OpenCCBridge/OpenCCBridge.swift +++ b/Source/3rdParty/OpenCCBridge/OpenCCBridge.swift @@ -1,20 +1,27 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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 Foundation @@ -25,28 +32,28 @@ import OpenCC /// Since SwiftyOpenCC only provide Swift classes, we create an NSObject subclass /// in Swift in order to bridge the Swift classes into our Objective-C++ project. public class OpenCCBridge: NSObject { - private static let shared = OpenCCBridge() - private var simplify: ChineseConverter? - private var traditionalize: ChineseConverter? - - private override init() { - try? simplify = ChineseConverter(options: .simplify) - try? traditionalize = ChineseConverter(options: [.traditionalize, .twStandard]) - super.init() - } + private static let shared = OpenCCBridge() + private var simplify: ChineseConverter? + private var traditionalize: ChineseConverter? - /// CrossConvert. - /// - /// - Parameter string: Text in Original Script. - /// - Returns: Text converted to Different Script. - @objc public static func crossConvert(_ string: String) -> String? { - switch ctlInputMethod.currentKeyHandler.inputMode { - case InputMode.imeModeCHS: - return shared.traditionalize?.convert(string) - case InputMode.imeModeCHT: - return shared.simplify?.convert(string) - default: - return string - } - } + private override init() { + try? simplify = ChineseConverter(options: .simplify) + try? traditionalize = ChineseConverter(options: [.traditionalize, .twStandard]) + super.init() + } + + /// CrossConvert. + /// + /// - Parameter string: Text in Original Script. + /// - Returns: Text converted to Different Script. + @objc public static func crossConvert(_ string: String) -> String? { + switch ctlInputMethod.currentKeyHandler.inputMode { + case InputMode.imeModeCHS: + return shared.traditionalize?.convert(string) + case InputMode.imeModeCHT: + return shared.simplify?.convert(string) + default: + return string + } + } } diff --git a/Source/Modules/AppDelegate.swift b/Source/Modules/AppDelegate.swift index 7d69bd11..912c789b 100644 --- a/Source/Modules/AppDelegate.swift +++ b/Source/Modules/AppDelegate.swift @@ -1,20 +1,27 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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 @@ -29,287 +36,346 @@ private let kNextCheckInterval: TimeInterval = 86400.0 private let kTimeoutInterval: TimeInterval = 60.0 struct VersionUpdateReport { - var siteUrl: URL? - var currentShortVersion: String = "" - var currentVersion: String = "" - var remoteShortVersion: String = "" - var remoteVersion: String = "" - var versionDescription: String = "" + var siteUrl: URL? + var currentShortVersion: String = "" + var currentVersion: String = "" + var remoteShortVersion: String = "" + var remoteVersion: String = "" + var versionDescription: String = "" } enum VersionUpdateApiResult { - case shouldUpdate(report: VersionUpdateReport) - case noNeedToUpdate - case ignored + case shouldUpdate(report: VersionUpdateReport) + case noNeedToUpdate + case ignored } enum VersionUpdateApiError: Error, LocalizedError { - case connectionError(message: String) + case connectionError(message: String) - var errorDescription: String? { - switch self { - case .connectionError(let message): - return String(format: NSLocalizedString("There may be no internet connection or the server failed to respond.\n\nError message: %@", comment: ""), message) - } - } + var errorDescription: String? { + switch self { + case .connectionError(let message): + return String( + format: NSLocalizedString( + "There may be no internet connection or the server failed to respond.\n\nError message: %@", + comment: ""), message) + } + } } struct VersionUpdateApi { - static func check(forced: Bool, callback: @escaping (Result) -> ()) -> URLSessionTask? { - guard let infoDict = Bundle.main.infoDictionary, - let updateInfoURLString = infoDict[kUpdateInfoEndpointKey] as? String, - let updateInfoURL = URL(string: updateInfoURLString) else { - return nil - } + static func check( + forced: Bool, callback: @escaping (Result) -> Void + ) -> URLSessionTask? { + guard let infoDict = Bundle.main.infoDictionary, + let updateInfoURLString = infoDict[kUpdateInfoEndpointKey] as? String, + let updateInfoURL = URL(string: updateInfoURLString) + else { + return nil + } - let request = URLRequest(url: updateInfoURL, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: kTimeoutInterval) - let task = URLSession.shared.dataTask(with: request) { data, response, error in - if let error = error { - DispatchQueue.main.async { - forced ? - callback(.failure(VersionUpdateApiError.connectionError(message: error.localizedDescription))) : - callback(.success(.ignored)) - } - return - } + let request = URLRequest( + url: updateInfoURL, cachePolicy: .reloadIgnoringLocalCacheData, + timeoutInterval: kTimeoutInterval) + let task = URLSession.shared.dataTask(with: request) { data, response, error in + if let error = error { + DispatchQueue.main.async { + forced + ? callback( + .failure( + VersionUpdateApiError.connectionError( + message: error.localizedDescription))) + : callback(.success(.ignored)) + } + return + } - do { - guard let plist = try PropertyListSerialization.propertyList(from: data ?? Data(), options: [], format: nil) as? [AnyHashable: Any], - let remoteVersion = plist[kCFBundleVersionKey] as? String, - let infoDict = Bundle.main.infoDictionary - else { - DispatchQueue.main.async { - forced ? callback(.success(.noNeedToUpdate)) : callback(.success(.ignored)) - } - return - } + do { + guard + let plist = try PropertyListSerialization.propertyList( + from: data ?? Data(), options: [], format: nil) as? [AnyHashable: Any], + let remoteVersion = plist[kCFBundleVersionKey] as? String, + let infoDict = Bundle.main.infoDictionary + else { + DispatchQueue.main.async { + forced + ? callback(.success(.noNeedToUpdate)) + : callback(.success(.ignored)) + } + return + } - // TODO: Validate info (e.g. bundle identifier) - // TODO: Use HTML to display change log, need a new key like UpdateInfoChangeLogURL for this + // TODO: Validate info (e.g. bundle identifier) + // TODO: Use HTML to display change log, need a new key like UpdateInfoChangeLogURL for this - let currentVersion = infoDict[kCFBundleVersionKey as String] as? String ?? "" - let result = currentVersion.compare(remoteVersion, options: .numeric, range: nil, locale: nil) + let currentVersion = infoDict[kCFBundleVersionKey as String] as? String ?? "" + let result = currentVersion.compare( + remoteVersion, options: .numeric, range: nil, locale: nil) - if result != .orderedAscending { - DispatchQueue.main.async { - forced ? callback(.success(.noNeedToUpdate)) : callback(.success(.ignored)) - } - IME.prtDebugIntel("vChewingDebug: Update // Order is not Ascending, assuming that there's no new version available.") - return - } - IME.prtDebugIntel("vChewingDebug: Update // New version detected, proceeding to the next phase.") - guard let siteInfoURLString = plist[kUpdateInfoSiteKey] as? String, - let siteInfoURL = URL(string: siteInfoURLString) - else { - DispatchQueue.main.async { - forced ? callback(.success(.noNeedToUpdate)) : callback(.success(.ignored)) - } - IME.prtDebugIntel("vChewingDebug: Update // Failed from retrieving / parsing URL intel.") - return - } - IME.prtDebugIntel("vChewingDebug: Update // URL intel retrieved, proceeding to the next phase.") - var report = VersionUpdateReport(siteUrl: siteInfoURL) - var versionDescription = "" - let versionDescriptions = plist[kVersionDescription] as? [AnyHashable: Any] - if let versionDescriptions = versionDescriptions { - var locale = "en" - let supportedLocales = ["en", "zh-Hant", "zh-Hans", "ja"] - let preferredTags = Bundle.preferredLocalizations(from: supportedLocales) - if let first = preferredTags.first { - locale = first - } - versionDescription = versionDescriptions[locale] as? String ?? versionDescriptions["en"] as? String ?? "" - if !versionDescription.isEmpty { - versionDescription = "\n\n" + versionDescription - } - } - report.currentShortVersion = infoDict["CFBundleShortVersionString"] as? String ?? "" - report.currentVersion = currentVersion - report.remoteShortVersion = plist["CFBundleShortVersionString"] as? String ?? "" - report.remoteVersion = remoteVersion - report.versionDescription = versionDescription - DispatchQueue.main.async { - callback(.success(.shouldUpdate(report: report))) - } - IME.prtDebugIntel("vChewingDebug: Update // Callbck Complete.") - } catch { - DispatchQueue.main.async { - forced ? callback(.success(.noNeedToUpdate)) : callback(.success(.ignored)) - } - } - } - task.resume() - return task - } + if result != .orderedAscending { + DispatchQueue.main.async { + forced + ? callback(.success(.noNeedToUpdate)) + : callback(.success(.ignored)) + } + IME.prtDebugIntel( + "vChewingDebug: Update // Order is not Ascending, assuming that there's no new version available." + ) + return + } + IME.prtDebugIntel( + "vChewingDebug: Update // New version detected, proceeding to the next phase.") + guard let siteInfoURLString = plist[kUpdateInfoSiteKey] as? String, + let siteInfoURL = URL(string: siteInfoURLString) + else { + DispatchQueue.main.async { + forced + ? callback(.success(.noNeedToUpdate)) + : callback(.success(.ignored)) + } + IME.prtDebugIntel( + "vChewingDebug: Update // Failed from retrieving / parsing URL intel.") + return + } + IME.prtDebugIntel( + "vChewingDebug: Update // URL intel retrieved, proceeding to the next phase.") + var report = VersionUpdateReport(siteUrl: siteInfoURL) + var versionDescription = "" + let versionDescriptions = plist[kVersionDescription] as? [AnyHashable: Any] + if let versionDescriptions = versionDescriptions { + var locale = "en" + let supportedLocales = ["en", "zh-Hant", "zh-Hans", "ja"] + let preferredTags = Bundle.preferredLocalizations(from: supportedLocales) + if let first = preferredTags.first { + locale = first + } + versionDescription = + versionDescriptions[locale] as? String ?? versionDescriptions["en"] + as? String ?? "" + if !versionDescription.isEmpty { + versionDescription = "\n\n" + versionDescription + } + } + report.currentShortVersion = infoDict["CFBundleShortVersionString"] as? String ?? "" + report.currentVersion = currentVersion + report.remoteShortVersion = plist["CFBundleShortVersionString"] as? String ?? "" + report.remoteVersion = remoteVersion + report.versionDescription = versionDescription + DispatchQueue.main.async { + callback(.success(.shouldUpdate(report: report))) + } + IME.prtDebugIntel("vChewingDebug: Update // Callbck Complete.") + } catch { + DispatchQueue.main.async { + forced ? callback(.success(.noNeedToUpdate)) : callback(.success(.ignored)) + } + } + } + task.resume() + return task + } } @objc(AppDelegate) -class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelegate, FSEventStreamHelperDelegate { - func helper(_ helper: FSEventStreamHelper, didReceive events: [FSEventStreamHelper.Event]) { - // 拖 100ms 再重載,畢竟有些有特殊需求的使用者可能會想使用巨型自訂語彙檔案。 - DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) { - if mgrPrefs.shouldAutoReloadUserDataFiles { - IME.initLangModels(userOnly: true) - } - } - } +class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelegate, + FSEventStreamHelperDelegate +{ + func helper(_ helper: FSEventStreamHelper, didReceive events: [FSEventStreamHelper.Event]) { + // 拖 100ms 再重載,畢竟有些有特殊需求的使用者可能會想使用巨型自訂語彙檔案。 + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) { + if mgrPrefs.shouldAutoReloadUserDataFiles { + IME.initLangModels(userOnly: true) + } + } + } - // let vChewingKeyLayoutBundle = Bundle.init(path: URL(fileURLWithPath: Bundle.main.resourcePath ?? "").appendingPathComponent("vChewingKeyLayout.bundle").path) + // let vChewingKeyLayoutBundle = Bundle.init(path: URL(fileURLWithPath: Bundle.main.resourcePath ?? "").appendingPathComponent("vChewingKeyLayout.bundle").path) - @IBOutlet weak var window: NSWindow? - private var ctlPrefWindowInstance: ctlPrefWindow? - private var ctlAboutWindowInstance: ctlAboutWindow? // New About Window - private var checkTask: URLSessionTask? - private var updateNextStepURL: URL? - private var fsStreamHelper = FSEventStreamHelper(path: mgrLangModel.dataFolderPath(isDefaultFolder: false), queue: DispatchQueue(label: "vChewing User Phrases")) - private var currentAlertType: String = "" + @IBOutlet weak var window: NSWindow? + private var ctlPrefWindowInstance: ctlPrefWindow? + private var ctlAboutWindowInstance: ctlAboutWindow? // New About Window + private var checkTask: URLSessionTask? + private var updateNextStepURL: URL? + private var fsStreamHelper = FSEventStreamHelper( + path: mgrLangModel.dataFolderPath(isDefaultFolder: false), + queue: DispatchQueue(label: "vChewing User Phrases")) + private var currentAlertType: String = "" - // 補上 dealloc - deinit { - ctlPrefWindowInstance = nil - ctlAboutWindowInstance = nil - checkTask = nil - updateNextStepURL = nil - fsStreamHelper.stop() - fsStreamHelper.delegate = nil - } + // 補上 dealloc + deinit { + ctlPrefWindowInstance = nil + ctlAboutWindowInstance = nil + checkTask = nil + updateNextStepURL = nil + fsStreamHelper.stop() + fsStreamHelper.delegate = nil + } - func applicationDidFinishLaunching(_ notification: Notification) { - IME.initLangModels(userOnly: false) - fsStreamHelper.delegate = self - _ = fsStreamHelper.start() + func applicationDidFinishLaunching(_ notification: Notification) { + IME.initLangModels(userOnly: false) + fsStreamHelper.delegate = self + _ = fsStreamHelper.start() - mgrPrefs.setMissingDefaults() - - // 只要使用者沒有勾選檢查更新、沒有主動做出要檢查更新的操作,就不要檢查更新。 - if (UserDefaults.standard.object(forKey: kCheckUpdateAutomatically) != nil) == true { - checkForUpdate() - } - } + mgrPrefs.setMissingDefaults() - @objc func showPreferences() { - if (ctlPrefWindowInstance == nil) { - ctlPrefWindowInstance = ctlPrefWindow.init(windowNibName: "frmPrefWindow") - } - ctlPrefWindowInstance?.window?.center() - ctlPrefWindowInstance?.window?.orderFrontRegardless() // 逼著屬性視窗往最前方顯示 - ctlPrefWindowInstance?.window?.level = .statusBar - ctlPrefWindowInstance?.window?.titlebarAppearsTransparent = true - NSApp.setActivationPolicy(.accessory) - } - - // New About Window - @objc func showAbout() { - if (ctlAboutWindowInstance == nil) { - ctlAboutWindowInstance = ctlAboutWindow.init(windowNibName: "frmAboutWindow") - } - ctlAboutWindowInstance?.window?.center() - ctlAboutWindowInstance?.window?.orderFrontRegardless() // 逼著關於視窗往最前方顯示 - ctlAboutWindowInstance?.window?.level = .statusBar - NSApp.setActivationPolicy(.accessory) - } + // 只要使用者沒有勾選檢查更新、沒有主動做出要檢查更新的操作,就不要檢查更新。 + if (UserDefaults.standard.object(forKey: kCheckUpdateAutomatically) != nil) == true { + checkForUpdate() + } + } - @objc(checkForUpdate) - func checkForUpdate() { - checkForUpdate(forced: false) - } + @objc func showPreferences() { + if ctlPrefWindowInstance == nil { + ctlPrefWindowInstance = ctlPrefWindow.init(windowNibName: "frmPrefWindow") + } + ctlPrefWindowInstance?.window?.center() + ctlPrefWindowInstance?.window?.orderFrontRegardless() // 逼著屬性視窗往最前方顯示 + ctlPrefWindowInstance?.window?.level = .statusBar + ctlPrefWindowInstance?.window?.titlebarAppearsTransparent = true + NSApp.setActivationPolicy(.accessory) + } - @objc(checkForUpdateForced:) - func checkForUpdate(forced: Bool) { + // New About Window + @objc func showAbout() { + if ctlAboutWindowInstance == nil { + ctlAboutWindowInstance = ctlAboutWindow.init(windowNibName: "frmAboutWindow") + } + ctlAboutWindowInstance?.window?.center() + ctlAboutWindowInstance?.window?.orderFrontRegardless() // 逼著關於視窗往最前方顯示 + ctlAboutWindowInstance?.window?.level = .statusBar + NSApp.setActivationPolicy(.accessory) + } - if checkTask != nil { - // busy - return - } + @objc(checkForUpdate) + func checkForUpdate() { + checkForUpdate(forced: false) + } - // time for update? - if !forced { - if UserDefaults.standard.bool(forKey: kCheckUpdateAutomatically) == false { - return - } - let now = Date() - let date = UserDefaults.standard.object(forKey: kNextUpdateCheckDateKey) as? Date ?? now - if now.compare(date) == .orderedAscending { - return - } - } + @objc(checkForUpdateForced:) + func checkForUpdate(forced: Bool) { - let nextUpdateDate = Date(timeInterval: kNextCheckInterval, since: Date()) - UserDefaults.standard.set(nextUpdateDate, forKey: kNextUpdateCheckDateKey) + if checkTask != nil { + // busy + return + } - checkTask = VersionUpdateApi.check(forced: forced) { [self] result in - defer { - self.checkTask = nil - } - switch result { - case .success(let apiResult): - switch apiResult { - case .shouldUpdate(let report): - self.updateNextStepURL = report.siteUrl - let content = String(format: NSLocalizedString("You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?%@", comment: ""), - report.currentShortVersion, - report.currentVersion, - report.remoteShortVersion, - report.remoteVersion, - report.versionDescription) - IME.prtDebugIntel("vChewingDebug: \(content)") - self.currentAlertType = "Update" - ctlNonModalAlertWindow.shared.show(title: NSLocalizedString("New Version Available", comment: ""), content: content, confirmButtonTitle: NSLocalizedString("Visit Website", comment: ""), cancelButtonTitle: NSLocalizedString("Not Now", comment: ""), cancelAsDefault: false, delegate: self) - NSApp.setActivationPolicy(.accessory) - case .noNeedToUpdate, .ignored: - break - } - case .failure(let error): - switch error { - case VersionUpdateApiError.connectionError(let message): - let title = NSLocalizedString("Update Check Failed", comment: "") - let content = String(format: NSLocalizedString("There may be no internet connection or the server failed to respond.\n\nError message: %@", comment: ""), message) - let buttonTitle = NSLocalizedString("Dismiss", comment: "") - IME.prtDebugIntel("vChewingDebug: \(content)") - self.currentAlertType = "Update" - ctlNonModalAlertWindow.shared.show(title: title, content: content, confirmButtonTitle: buttonTitle, cancelButtonTitle: nil, cancelAsDefault: false, delegate: nil) - NSApp.setActivationPolicy(.accessory) - default: - break - } - } - } - } + // time for update? + if !forced { + if UserDefaults.standard.bool(forKey: kCheckUpdateAutomatically) == false { + return + } + let now = Date() + let date = UserDefaults.standard.object(forKey: kNextUpdateCheckDateKey) as? Date ?? now + if now.compare(date) == .orderedAscending { + return + } + } - func selfUninstall() { - self.currentAlertType = "Uninstall" - let content = String(format: NSLocalizedString("This will remove vChewing Input Method from this user account, requiring your confirmation.", comment: "")) - ctlNonModalAlertWindow.shared.show(title: NSLocalizedString("Uninstallation", comment: ""), content: content, confirmButtonTitle: NSLocalizedString("OK", comment: ""), cancelButtonTitle: NSLocalizedString("Not Now", comment: ""), cancelAsDefault: false, delegate: self) - NSApp.setActivationPolicy(.accessory) - } + let nextUpdateDate = Date(timeInterval: kNextCheckInterval, since: Date()) + UserDefaults.standard.set(nextUpdateDate, forKey: kNextUpdateCheckDateKey) - func ctlNonModalAlertWindowDidConfirm(_ controller: ctlNonModalAlertWindow) { - switch self.currentAlertType { - case "Uninstall": - NSWorkspace.shared.openFile(mgrLangModel.dataFolderPath(isDefaultFolder: true), withApplication: "Finder") - IME.uninstall(isSudo: false, selfKill: true) - case "Update": - if let updateNextStepURL = self.updateNextStepURL { - NSWorkspace.shared.open(updateNextStepURL) - } - self.updateNextStepURL = nil - default: - break - } - } + checkTask = VersionUpdateApi.check(forced: forced) { [self] result in + defer { + self.checkTask = nil + } + switch result { + case .success(let apiResult): + switch apiResult { + case .shouldUpdate(let report): + self.updateNextStepURL = report.siteUrl + let content = String( + format: NSLocalizedString( + "You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?%@", + comment: ""), + report.currentShortVersion, + report.currentVersion, + report.remoteShortVersion, + report.remoteVersion, + report.versionDescription) + IME.prtDebugIntel("vChewingDebug: \(content)") + self.currentAlertType = "Update" + ctlNonModalAlertWindow.shared.show( + title: NSLocalizedString( + "New Version Available", comment: ""), + content: content, + confirmButtonTitle: NSLocalizedString( + "Visit Website", comment: ""), + cancelButtonTitle: NSLocalizedString( + "Not Now", comment: ""), + cancelAsDefault: false, + delegate: self) + NSApp.setActivationPolicy(.accessory) + case .noNeedToUpdate, .ignored: + break + } + case .failure(let error): + switch error { + case VersionUpdateApiError.connectionError(let message): + let title = NSLocalizedString( + "Update Check Failed", comment: "") + let content = String( + format: NSLocalizedString( + "There may be no internet connection or the server failed to respond.\n\nError message: %@", + comment: ""), message) + let buttonTitle = NSLocalizedString("Dismiss", comment: "") + IME.prtDebugIntel("vChewingDebug: \(content)") + self.currentAlertType = "Update" + ctlNonModalAlertWindow.shared.show( + title: title, content: content, + confirmButtonTitle: buttonTitle, + cancelButtonTitle: nil, + cancelAsDefault: false, delegate: nil) + NSApp.setActivationPolicy(.accessory) + default: + break + } + } + } + } - func ctlNonModalAlertWindowDidCancel(_ controller: ctlNonModalAlertWindow) { - switch self.currentAlertType { - case "Update": - self.updateNextStepURL = nil - default: - break - } - } + func selfUninstall() { + self.currentAlertType = "Uninstall" + let content = String( + format: NSLocalizedString( + "This will remove vChewing Input Method from this user account, requiring your confirmation.", + comment: "")) + ctlNonModalAlertWindow.shared.show( + title: NSLocalizedString("Uninstallation", comment: ""), content: content, + confirmButtonTitle: NSLocalizedString("OK", comment: ""), + cancelButtonTitle: NSLocalizedString("Not Now", comment: ""), cancelAsDefault: false, + delegate: self) + NSApp.setActivationPolicy(.accessory) + } - // New About Window - @IBAction func about(_ sender: Any) { - (NSApp.delegate as? AppDelegate)?.showAbout() - NSApplication.shared.activate(ignoringOtherApps: true) - } + func ctlNonModalAlertWindowDidConfirm(_ controller: ctlNonModalAlertWindow) { + switch self.currentAlertType { + case "Uninstall": + NSWorkspace.shared.openFile( + mgrLangModel.dataFolderPath(isDefaultFolder: true), withApplication: "Finder") + IME.uninstall(isSudo: false, selfKill: true) + case "Update": + if let updateNextStepURL = self.updateNextStepURL { + NSWorkspace.shared.open(updateNextStepURL) + } + self.updateNextStepURL = nil + default: + break + } + } + + func ctlNonModalAlertWindowDidCancel(_ controller: ctlNonModalAlertWindow) { + switch self.currentAlertType { + case "Update": + self.updateNextStepURL = nil + default: + break + } + } + + // New About Window + @IBAction func about(_ sender: Any) { + (NSApp.delegate as? AppDelegate)?.showAbout() + NSApplication.shared.activate(ignoringOtherApps: true) + } } diff --git a/Source/Modules/ControllerModules/AppleKeyboardConverter.swift b/Source/Modules/ControllerModules/AppleKeyboardConverter.swift index 87bd1888..1b3c4618 100644 --- a/Source/Modules/ControllerModules/AppleKeyboardConverter.swift +++ b/Source/Modules/ControllerModules/AppleKeyboardConverter.swift @@ -1,329 +1,339 @@ // Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ import Cocoa @objc class AppleKeyboardConverter: NSObject { - @objc class func isDynamicBaseKeyboardLayoutEnabled() -> Bool { - switch mgrPrefs.basisKeyboardLayout { - case "com.apple.keylayout.ZhuyinBopomofo": - return true - case "com.apple.keylayout.ZhuyinEten": - return true - case "org.atelierInmu.vChewing.keyLayouts.vchewingdachen": - return true - case "org.atelierInmu.vChewing.keyLayouts.vchewingmitac": - return true - case "org.atelierInmu.vChewing.keyLayouts.vchewingibm": - return true - case "org.atelierInmu.vChewing.keyLayouts.vchewingseigyou": - return true - case "org.atelierInmu.vChewing.keyLayouts.vchewingeten": - return true - case "org.unknown.keylayout.vChewingDachen": - return true - case "org.unknown.keylayout.vChewingFakeSeigyou": - return true - case "org.unknown.keylayout.vChewingETen": - return true - case "org.unknown.keylayout.vChewingIBM": - return true - case "org.unknown.keylayout.vChewingMiTAC": - return true - default: - return false - } - } - // 處理 Apple 注音鍵盤佈局類型。 - @objc class func cnvApple2ABC(_ charCode: UniChar) -> UniChar { - var charCode = charCode - // 在按鍵資訊被送往 OVMandarin 之前,先轉換為可以被 OVMandarin 正常處理的資訊。 - if self.isDynamicBaseKeyboardLayoutEnabled() { - // 針對不同的 Apple 動態鍵盤佈局糾正大寫英文輸入。 - switch mgrPrefs.basisKeyboardLayout { - case "com.apple.keylayout.ZhuyinBopomofo": do { - if (charCode == 97) {charCode = UniChar(65)} - if (charCode == 98) {charCode = UniChar(66)} - if (charCode == 99) {charCode = UniChar(67)} - if (charCode == 100) {charCode = UniChar(68)} - if (charCode == 101) {charCode = UniChar(69)} - if (charCode == 102) {charCode = UniChar(70)} - if (charCode == 103) {charCode = UniChar(71)} - if (charCode == 104) {charCode = UniChar(72)} - if (charCode == 105) {charCode = UniChar(73)} - if (charCode == 106) {charCode = UniChar(74)} - if (charCode == 107) {charCode = UniChar(75)} - if (charCode == 108) {charCode = UniChar(76)} - if (charCode == 109) {charCode = UniChar(77)} - if (charCode == 110) {charCode = UniChar(78)} - if (charCode == 111) {charCode = UniChar(79)} - if (charCode == 112) {charCode = UniChar(80)} - if (charCode == 113) {charCode = UniChar(81)} - if (charCode == 114) {charCode = UniChar(82)} - if (charCode == 115) {charCode = UniChar(83)} - if (charCode == 116) {charCode = UniChar(84)} - if (charCode == 117) {charCode = UniChar(85)} - if (charCode == 118) {charCode = UniChar(86)} - if (charCode == 119) {charCode = UniChar(87)} - if (charCode == 120) {charCode = UniChar(88)} - if (charCode == 121) {charCode = UniChar(89)} - if (charCode == 122) {charCode = UniChar(90)} - } - case "com.apple.keylayout.ZhuyinEten": do { - if (charCode == 65345) {charCode = UniChar(65)} - if (charCode == 65346) {charCode = UniChar(66)} - if (charCode == 65347) {charCode = UniChar(67)} - if (charCode == 65348) {charCode = UniChar(68)} - if (charCode == 65349) {charCode = UniChar(69)} - if (charCode == 65350) {charCode = UniChar(70)} - if (charCode == 65351) {charCode = UniChar(71)} - if (charCode == 65352) {charCode = UniChar(72)} - if (charCode == 65353) {charCode = UniChar(73)} - if (charCode == 65354) {charCode = UniChar(74)} - if (charCode == 65355) {charCode = UniChar(75)} - if (charCode == 65356) {charCode = UniChar(76)} - if (charCode == 65357) {charCode = UniChar(77)} - if (charCode == 65358) {charCode = UniChar(78)} - if (charCode == 65359) {charCode = UniChar(79)} - if (charCode == 65360) {charCode = UniChar(80)} - if (charCode == 65361) {charCode = UniChar(81)} - if (charCode == 65362) {charCode = UniChar(82)} - if (charCode == 65363) {charCode = UniChar(83)} - if (charCode == 65364) {charCode = UniChar(84)} - if (charCode == 65365) {charCode = UniChar(85)} - if (charCode == 65366) {charCode = UniChar(86)} - if (charCode == 65367) {charCode = UniChar(87)} - if (charCode == 65368) {charCode = UniChar(88)} - if (charCode == 65369) {charCode = UniChar(89)} - if (charCode == 65370) {charCode = UniChar(90)} - } - default: break - } - // 注音鍵群。 - if (charCode == 12573) {charCode = UniChar(44)} - if (charCode == 12582) {charCode = UniChar(45)} - if (charCode == 12577) {charCode = UniChar(46)} - if (charCode == 12581) {charCode = UniChar(47)} - if (charCode == 12578) {charCode = UniChar(48)} - if (charCode == 12549) {charCode = UniChar(49)} - if (charCode == 12553) {charCode = UniChar(50)} - if (charCode == 711) {charCode = UniChar(51)} - if (charCode == 715) {charCode = UniChar(52)} - if (charCode == 12563) {charCode = UniChar(53)} - if (charCode == 714) {charCode = UniChar(54)} - if (charCode == 729) {charCode = UniChar(55)} - if (charCode == 12570) {charCode = UniChar(56)} - if (charCode == 12574) {charCode = UniChar(57)} - if (charCode == 12580) {charCode = UniChar(59)} - if (charCode == 12551) {charCode = UniChar(97)} - if (charCode == 12566) {charCode = UniChar(98)} - if (charCode == 12559) {charCode = UniChar(99)} - if (charCode == 12558) {charCode = UniChar(100)} - if (charCode == 12557) {charCode = UniChar(101)} - if (charCode == 12561) {charCode = UniChar(102)} - if (charCode == 12565) {charCode = UniChar(103)} - if (charCode == 12568) {charCode = UniChar(104)} - if (charCode == 12571) {charCode = UniChar(105)} - if (charCode == 12584) {charCode = UniChar(106)} - if (charCode == 12572) {charCode = UniChar(107)} - if (charCode == 12576) {charCode = UniChar(108)} - if (charCode == 12585) {charCode = UniChar(109)} - if (charCode == 12569) {charCode = UniChar(110)} - if (charCode == 12575) {charCode = UniChar(111)} - if (charCode == 12579) {charCode = UniChar(112)} - if (charCode == 12550) {charCode = UniChar(113)} - if (charCode == 12560) {charCode = UniChar(114)} - if (charCode == 12555) {charCode = UniChar(115)} - if (charCode == 12564) {charCode = UniChar(116)} - if (charCode == 12583) {charCode = UniChar(117)} - if (charCode == 12562) {charCode = UniChar(118)} - if (charCode == 12554) {charCode = UniChar(119)} - if (charCode == 12556) {charCode = UniChar(120)} - if (charCode == 12567) {charCode = UniChar(121)} - if (charCode == 12552) {charCode = UniChar(122)} - // 除了數字鍵區以外的標點符號。 - if (charCode == 12289) {charCode = UniChar(92)} - if (charCode == 12300) {charCode = UniChar(91)} - if (charCode == 12301) {charCode = UniChar(93)} - if (charCode == 12302) {charCode = UniChar(123)} - if (charCode == 12303) {charCode = UniChar(125)} - if (charCode == 65292) {charCode = UniChar(60)} - if (charCode == 12290) {charCode = UniChar(62)} - // 摁了 SHIFT 之後的數字區的符號。 - if (charCode == 65281) {charCode = UniChar(33)} - if (charCode == 65312) {charCode = UniChar(64)} - if (charCode == 65283) {charCode = UniChar(35)} - if (charCode == 65284) {charCode = UniChar(36)} - if (charCode == 65285) {charCode = UniChar(37)} - if (charCode == 65087) {charCode = UniChar(94)} - if (charCode == 65286) {charCode = UniChar(38)} - if (charCode == 65290) {charCode = UniChar(42)} - if (charCode == 65288) {charCode = UniChar(40)} - if (charCode == 65289) {charCode = UniChar(41)} - // 摁了 Alt 的符號。 - if (charCode == 8212) {charCode = UniChar(45)} - // Apple 倚天注音佈局追加符號糾正項目。 - if mgrPrefs.basisKeyboardLayout == "com.apple.keylayout.ZhuyinEten" { - if (charCode == 65343) {charCode = UniChar(95)} - if (charCode == 65306) {charCode = UniChar(58)} - if (charCode == 65311) {charCode = UniChar(63)} - if (charCode == 65291) {charCode = UniChar(43)} - if (charCode == 65372) {charCode = UniChar(124)} - } - } - return charCode - } + @objc class func isDynamicBaseKeyboardLayoutEnabled() -> Bool { + switch mgrPrefs.basisKeyboardLayout { + case "com.apple.keylayout.ZhuyinBopomofo": + return true + case "com.apple.keylayout.ZhuyinEten": + return true + case "org.atelierInmu.vChewing.keyLayouts.vchewingdachen": + return true + case "org.atelierInmu.vChewing.keyLayouts.vchewingmitac": + return true + case "org.atelierInmu.vChewing.keyLayouts.vchewingibm": + return true + case "org.atelierInmu.vChewing.keyLayouts.vchewingseigyou": + return true + case "org.atelierInmu.vChewing.keyLayouts.vchewingeten": + return true + case "org.unknown.keylayout.vChewingDachen": + return true + case "org.unknown.keylayout.vChewingFakeSeigyou": + return true + case "org.unknown.keylayout.vChewingETen": + return true + case "org.unknown.keylayout.vChewingIBM": + return true + case "org.unknown.keylayout.vChewingMiTAC": + return true + default: + return false + } + } + // 處理 Apple 注音鍵盤佈局類型。 + @objc class func cnvApple2ABC(_ charCode: UniChar) -> UniChar { + var charCode = charCode + // 在按鍵資訊被送往 OVMandarin 之前,先轉換為可以被 OVMandarin 正常處理的資訊。 + if self.isDynamicBaseKeyboardLayoutEnabled() { + // 針對不同的 Apple 動態鍵盤佈局糾正大寫英文輸入。 + switch mgrPrefs.basisKeyboardLayout { + case "com.apple.keylayout.ZhuyinBopomofo": + do { + if charCode == 97 { charCode = UniChar(65) } + if charCode == 98 { charCode = UniChar(66) } + if charCode == 99 { charCode = UniChar(67) } + if charCode == 100 { charCode = UniChar(68) } + if charCode == 101 { charCode = UniChar(69) } + if charCode == 102 { charCode = UniChar(70) } + if charCode == 103 { charCode = UniChar(71) } + if charCode == 104 { charCode = UniChar(72) } + if charCode == 105 { charCode = UniChar(73) } + if charCode == 106 { charCode = UniChar(74) } + if charCode == 107 { charCode = UniChar(75) } + if charCode == 108 { charCode = UniChar(76) } + if charCode == 109 { charCode = UniChar(77) } + if charCode == 110 { charCode = UniChar(78) } + if charCode == 111 { charCode = UniChar(79) } + if charCode == 112 { charCode = UniChar(80) } + if charCode == 113 { charCode = UniChar(81) } + if charCode == 114 { charCode = UniChar(82) } + if charCode == 115 { charCode = UniChar(83) } + if charCode == 116 { charCode = UniChar(84) } + if charCode == 117 { charCode = UniChar(85) } + if charCode == 118 { charCode = UniChar(86) } + if charCode == 119 { charCode = UniChar(87) } + if charCode == 120 { charCode = UniChar(88) } + if charCode == 121 { charCode = UniChar(89) } + if charCode == 122 { charCode = UniChar(90) } + } + case "com.apple.keylayout.ZhuyinEten": + do { + if charCode == 65345 { charCode = UniChar(65) } + if charCode == 65346 { charCode = UniChar(66) } + if charCode == 65347 { charCode = UniChar(67) } + if charCode == 65348 { charCode = UniChar(68) } + if charCode == 65349 { charCode = UniChar(69) } + if charCode == 65350 { charCode = UniChar(70) } + if charCode == 65351 { charCode = UniChar(71) } + if charCode == 65352 { charCode = UniChar(72) } + if charCode == 65353 { charCode = UniChar(73) } + if charCode == 65354 { charCode = UniChar(74) } + if charCode == 65355 { charCode = UniChar(75) } + if charCode == 65356 { charCode = UniChar(76) } + if charCode == 65357 { charCode = UniChar(77) } + if charCode == 65358 { charCode = UniChar(78) } + if charCode == 65359 { charCode = UniChar(79) } + if charCode == 65360 { charCode = UniChar(80) } + if charCode == 65361 { charCode = UniChar(81) } + if charCode == 65362 { charCode = UniChar(82) } + if charCode == 65363 { charCode = UniChar(83) } + if charCode == 65364 { charCode = UniChar(84) } + if charCode == 65365 { charCode = UniChar(85) } + if charCode == 65366 { charCode = UniChar(86) } + if charCode == 65367 { charCode = UniChar(87) } + if charCode == 65368 { charCode = UniChar(88) } + if charCode == 65369 { charCode = UniChar(89) } + if charCode == 65370 { charCode = UniChar(90) } + } + default: break + } + // 注音鍵群。 + if charCode == 12573 { charCode = UniChar(44) } + if charCode == 12582 { charCode = UniChar(45) } + if charCode == 12577 { charCode = UniChar(46) } + if charCode == 12581 { charCode = UniChar(47) } + if charCode == 12578 { charCode = UniChar(48) } + if charCode == 12549 { charCode = UniChar(49) } + if charCode == 12553 { charCode = UniChar(50) } + if charCode == 711 { charCode = UniChar(51) } + if charCode == 715 { charCode = UniChar(52) } + if charCode == 12563 { charCode = UniChar(53) } + if charCode == 714 { charCode = UniChar(54) } + if charCode == 729 { charCode = UniChar(55) } + if charCode == 12570 { charCode = UniChar(56) } + if charCode == 12574 { charCode = UniChar(57) } + if charCode == 12580 { charCode = UniChar(59) } + if charCode == 12551 { charCode = UniChar(97) } + if charCode == 12566 { charCode = UniChar(98) } + if charCode == 12559 { charCode = UniChar(99) } + if charCode == 12558 { charCode = UniChar(100) } + if charCode == 12557 { charCode = UniChar(101) } + if charCode == 12561 { charCode = UniChar(102) } + if charCode == 12565 { charCode = UniChar(103) } + if charCode == 12568 { charCode = UniChar(104) } + if charCode == 12571 { charCode = UniChar(105) } + if charCode == 12584 { charCode = UniChar(106) } + if charCode == 12572 { charCode = UniChar(107) } + if charCode == 12576 { charCode = UniChar(108) } + if charCode == 12585 { charCode = UniChar(109) } + if charCode == 12569 { charCode = UniChar(110) } + if charCode == 12575 { charCode = UniChar(111) } + if charCode == 12579 { charCode = UniChar(112) } + if charCode == 12550 { charCode = UniChar(113) } + if charCode == 12560 { charCode = UniChar(114) } + if charCode == 12555 { charCode = UniChar(115) } + if charCode == 12564 { charCode = UniChar(116) } + if charCode == 12583 { charCode = UniChar(117) } + if charCode == 12562 { charCode = UniChar(118) } + if charCode == 12554 { charCode = UniChar(119) } + if charCode == 12556 { charCode = UniChar(120) } + if charCode == 12567 { charCode = UniChar(121) } + if charCode == 12552 { charCode = UniChar(122) } + // 除了數字鍵區以外的標點符號。 + if charCode == 12289 { charCode = UniChar(92) } + if charCode == 12300 { charCode = UniChar(91) } + if charCode == 12301 { charCode = UniChar(93) } + if charCode == 12302 { charCode = UniChar(123) } + if charCode == 12303 { charCode = UniChar(125) } + if charCode == 65292 { charCode = UniChar(60) } + if charCode == 12290 { charCode = UniChar(62) } + // 摁了 SHIFT 之後的數字區的符號。 + if charCode == 65281 { charCode = UniChar(33) } + if charCode == 65312 { charCode = UniChar(64) } + if charCode == 65283 { charCode = UniChar(35) } + if charCode == 65284 { charCode = UniChar(36) } + if charCode == 65285 { charCode = UniChar(37) } + if charCode == 65087 { charCode = UniChar(94) } + if charCode == 65286 { charCode = UniChar(38) } + if charCode == 65290 { charCode = UniChar(42) } + if charCode == 65288 { charCode = UniChar(40) } + if charCode == 65289 { charCode = UniChar(41) } + // 摁了 Alt 的符號。 + if charCode == 8212 { charCode = UniChar(45) } + // Apple 倚天注音佈局追加符號糾正項目。 + if mgrPrefs.basisKeyboardLayout == "com.apple.keylayout.ZhuyinEten" { + if charCode == 65343 { charCode = UniChar(95) } + if charCode == 65306 { charCode = UniChar(58) } + if charCode == 65311 { charCode = UniChar(63) } + if charCode == 65291 { charCode = UniChar(43) } + if charCode == 65372 { charCode = UniChar(124) } + } + } + return charCode + } - @objc class func cnvStringApple2ABC(_ strProcessed: String) -> String { - var strProcessed = strProcessed - if self.isDynamicBaseKeyboardLayoutEnabled() { - // 針對不同的 Apple 動態鍵盤佈局糾正大寫英文輸入。 - switch mgrPrefs.basisKeyboardLayout { - case "com.apple.keylayout.ZhuyinBopomofo": do { - if (strProcessed == "a") {strProcessed = "A"} - if (strProcessed == "b") {strProcessed = "B"} - if (strProcessed == "c") {strProcessed = "C"} - if (strProcessed == "d") {strProcessed = "D"} - if (strProcessed == "e") {strProcessed = "E"} - if (strProcessed == "f") {strProcessed = "F"} - if (strProcessed == "g") {strProcessed = "G"} - if (strProcessed == "h") {strProcessed = "H"} - if (strProcessed == "i") {strProcessed = "I"} - if (strProcessed == "j") {strProcessed = "J"} - if (strProcessed == "k") {strProcessed = "K"} - if (strProcessed == "l") {strProcessed = "L"} - if (strProcessed == "m") {strProcessed = "M"} - if (strProcessed == "n") {strProcessed = "N"} - if (strProcessed == "o") {strProcessed = "O"} - if (strProcessed == "p") {strProcessed = "P"} - if (strProcessed == "q") {strProcessed = "Q"} - if (strProcessed == "r") {strProcessed = "R"} - if (strProcessed == "s") {strProcessed = "S"} - if (strProcessed == "t") {strProcessed = "T"} - if (strProcessed == "u") {strProcessed = "U"} - if (strProcessed == "v") {strProcessed = "V"} - if (strProcessed == "w") {strProcessed = "W"} - if (strProcessed == "x") {strProcessed = "X"} - if (strProcessed == "y") {strProcessed = "Y"} - if (strProcessed == "z") {strProcessed = "Z"} - } - case "com.apple.keylayout.ZhuyinEten": do { - if (strProcessed == "a") {strProcessed = "A"} - if (strProcessed == "b") {strProcessed = "B"} - if (strProcessed == "c") {strProcessed = "C"} - if (strProcessed == "d") {strProcessed = "D"} - if (strProcessed == "e") {strProcessed = "E"} - if (strProcessed == "f") {strProcessed = "F"} - if (strProcessed == "g") {strProcessed = "G"} - if (strProcessed == "h") {strProcessed = "H"} - if (strProcessed == "i") {strProcessed = "I"} - if (strProcessed == "j") {strProcessed = "J"} - if (strProcessed == "k") {strProcessed = "K"} - if (strProcessed == "l") {strProcessed = "L"} - if (strProcessed == "m") {strProcessed = "M"} - if (strProcessed == "n") {strProcessed = "N"} - if (strProcessed == "o") {strProcessed = "O"} - if (strProcessed == "p") {strProcessed = "P"} - if (strProcessed == "q") {strProcessed = "Q"} - if (strProcessed == "r") {strProcessed = "R"} - if (strProcessed == "s") {strProcessed = "S"} - if (strProcessed == "t") {strProcessed = "T"} - if (strProcessed == "u") {strProcessed = "U"} - if (strProcessed == "v") {strProcessed = "V"} - if (strProcessed == "w") {strProcessed = "W"} - if (strProcessed == "x") {strProcessed = "X"} - if (strProcessed == "y") {strProcessed = "Y"} - if (strProcessed == "z") {strProcessed = "Z"} - } - default: break - } - // 注音鍵群。 - if (strProcessed == "ㄝ") {strProcessed = ","} - if (strProcessed == "ㄦ") {strProcessed = "-"} - if (strProcessed == "ㄡ") {strProcessed = "."} - if (strProcessed == "ㄥ") {strProcessed = "/"} - if (strProcessed == "ㄢ") {strProcessed = "0"} - if (strProcessed == "ㄅ") {strProcessed = "1"} - if (strProcessed == "ㄉ") {strProcessed = "2"} - if (strProcessed == "ˇ") {strProcessed = "3"} - if (strProcessed == "ˋ") {strProcessed = "4"} - if (strProcessed == "ㄓ") {strProcessed = "5"} - if (strProcessed == "ˊ") {strProcessed = "6"} - if (strProcessed == "˙") {strProcessed = "7"} - if (strProcessed == "ㄚ") {strProcessed = "8"} - if (strProcessed == "ㄞ") {strProcessed = "9"} - if (strProcessed == "ㄤ") {strProcessed = ";"} - if (strProcessed == "ㄇ") {strProcessed = "a"} - if (strProcessed == "ㄖ") {strProcessed = "b"} - if (strProcessed == "ㄏ") {strProcessed = "c"} - if (strProcessed == "ㄎ") {strProcessed = "d"} - if (strProcessed == "ㄍ") {strProcessed = "e"} - if (strProcessed == "ㄑ") {strProcessed = "f"} - if (strProcessed == "ㄕ") {strProcessed = "g"} - if (strProcessed == "ㄘ") {strProcessed = "h"} - if (strProcessed == "ㄛ") {strProcessed = "i"} - if (strProcessed == "ㄨ") {strProcessed = "j"} - if (strProcessed == "ㄜ") {strProcessed = "k"} - if (strProcessed == "ㄠ") {strProcessed = "l"} - if (strProcessed == "ㄩ") {strProcessed = "m"} - if (strProcessed == "ㄙ") {strProcessed = "n"} - if (strProcessed == "ㄟ") {strProcessed = "o"} - if (strProcessed == "ㄣ") {strProcessed = "p"} - if (strProcessed == "ㄆ") {strProcessed = "q"} - if (strProcessed == "ㄐ") {strProcessed = "r"} - if (strProcessed == "ㄋ") {strProcessed = "s"} - if (strProcessed == "ㄔ") {strProcessed = "t"} - if (strProcessed == "ㄧ") {strProcessed = "u"} - if (strProcessed == "ㄒ") {strProcessed = "v"} - if (strProcessed == "ㄊ") {strProcessed = "w"} - if (strProcessed == "ㄌ") {strProcessed = "x"} - if (strProcessed == "ㄗ") {strProcessed = "y"} - if (strProcessed == "ㄈ") {strProcessed = "z"} - // 除了數字鍵區以外的標點符號。 - if (strProcessed == "、") {strProcessed = "\\"} - if (strProcessed == "「") {strProcessed = "["} - if (strProcessed == "」") {strProcessed = "]"} - if (strProcessed == "『") {strProcessed = "{"} - if (strProcessed == "』") {strProcessed = "}"} - if (strProcessed == ",") {strProcessed = "<"} - if (strProcessed == "。") {strProcessed = ">"} - // 摁了 SHIFT 之後的數字區的符號。 - if (strProcessed == "!") {strProcessed = "!"} - if (strProcessed == "@") {strProcessed = "@"} - if (strProcessed == "#") {strProcessed = "#"} - if (strProcessed == "$") {strProcessed = "$"} - if (strProcessed == "%") {strProcessed = "%"} - if (strProcessed == "︿") {strProcessed = "^"} - if (strProcessed == "&") {strProcessed = "&"} - if (strProcessed == "*") {strProcessed = "*"} - if (strProcessed == "(") {strProcessed = "("} - if (strProcessed == ")") {strProcessed = ")"} - // 摁了 Alt 的符號。 - if (strProcessed == "—") {strProcessed = "-"} - // Apple 倚天注音佈局追加符號糾正項目。 - if mgrPrefs.basisKeyboardLayout == "com.apple.keylayout.ZhuyinEten" { - if (strProcessed == "_") {strProcessed = "_"} - if (strProcessed == ":") {strProcessed = ":"} - if (strProcessed == "?") {strProcessed = "?"} - if (strProcessed == "+") {strProcessed = "+"} - if (strProcessed == "|") {strProcessed = "|"} - } - } - return strProcessed - } + @objc class func cnvStringApple2ABC(_ strProcessed: String) -> String { + var strProcessed = strProcessed + if self.isDynamicBaseKeyboardLayoutEnabled() { + // 針對不同的 Apple 動態鍵盤佈局糾正大寫英文輸入。 + switch mgrPrefs.basisKeyboardLayout { + case "com.apple.keylayout.ZhuyinBopomofo": + do { + if strProcessed == "a" { strProcessed = "A" } + if strProcessed == "b" { strProcessed = "B" } + if strProcessed == "c" { strProcessed = "C" } + if strProcessed == "d" { strProcessed = "D" } + if strProcessed == "e" { strProcessed = "E" } + if strProcessed == "f" { strProcessed = "F" } + if strProcessed == "g" { strProcessed = "G" } + if strProcessed == "h" { strProcessed = "H" } + if strProcessed == "i" { strProcessed = "I" } + if strProcessed == "j" { strProcessed = "J" } + if strProcessed == "k" { strProcessed = "K" } + if strProcessed == "l" { strProcessed = "L" } + if strProcessed == "m" { strProcessed = "M" } + if strProcessed == "n" { strProcessed = "N" } + if strProcessed == "o" { strProcessed = "O" } + if strProcessed == "p" { strProcessed = "P" } + if strProcessed == "q" { strProcessed = "Q" } + if strProcessed == "r" { strProcessed = "R" } + if strProcessed == "s" { strProcessed = "S" } + if strProcessed == "t" { strProcessed = "T" } + if strProcessed == "u" { strProcessed = "U" } + if strProcessed == "v" { strProcessed = "V" } + if strProcessed == "w" { strProcessed = "W" } + if strProcessed == "x" { strProcessed = "X" } + if strProcessed == "y" { strProcessed = "Y" } + if strProcessed == "z" { strProcessed = "Z" } + } + case "com.apple.keylayout.ZhuyinEten": + do { + if strProcessed == "a" { strProcessed = "A" } + if strProcessed == "b" { strProcessed = "B" } + if strProcessed == "c" { strProcessed = "C" } + if strProcessed == "d" { strProcessed = "D" } + if strProcessed == "e" { strProcessed = "E" } + if strProcessed == "f" { strProcessed = "F" } + if strProcessed == "g" { strProcessed = "G" } + if strProcessed == "h" { strProcessed = "H" } + if strProcessed == "i" { strProcessed = "I" } + if strProcessed == "j" { strProcessed = "J" } + if strProcessed == "k" { strProcessed = "K" } + if strProcessed == "l" { strProcessed = "L" } + if strProcessed == "m" { strProcessed = "M" } + if strProcessed == "n" { strProcessed = "N" } + if strProcessed == "o" { strProcessed = "O" } + if strProcessed == "p" { strProcessed = "P" } + if strProcessed == "q" { strProcessed = "Q" } + if strProcessed == "r" { strProcessed = "R" } + if strProcessed == "s" { strProcessed = "S" } + if strProcessed == "t" { strProcessed = "T" } + if strProcessed == "u" { strProcessed = "U" } + if strProcessed == "v" { strProcessed = "V" } + if strProcessed == "w" { strProcessed = "W" } + if strProcessed == "x" { strProcessed = "X" } + if strProcessed == "y" { strProcessed = "Y" } + if strProcessed == "z" { strProcessed = "Z" } + } + default: break + } + // 注音鍵群。 + if strProcessed == "ㄝ" { strProcessed = "," } + if strProcessed == "ㄦ" { strProcessed = "-" } + if strProcessed == "ㄡ" { strProcessed = "." } + if strProcessed == "ㄥ" { strProcessed = "/" } + if strProcessed == "ㄢ" { strProcessed = "0" } + if strProcessed == "ㄅ" { strProcessed = "1" } + if strProcessed == "ㄉ" { strProcessed = "2" } + if strProcessed == "ˇ" { strProcessed = "3" } + if strProcessed == "ˋ" { strProcessed = "4" } + if strProcessed == "ㄓ" { strProcessed = "5" } + if strProcessed == "ˊ" { strProcessed = "6" } + if strProcessed == "˙" { strProcessed = "7" } + if strProcessed == "ㄚ" { strProcessed = "8" } + if strProcessed == "ㄞ" { strProcessed = "9" } + if strProcessed == "ㄤ" { strProcessed = ";" } + if strProcessed == "ㄇ" { strProcessed = "a" } + if strProcessed == "ㄖ" { strProcessed = "b" } + if strProcessed == "ㄏ" { strProcessed = "c" } + if strProcessed == "ㄎ" { strProcessed = "d" } + if strProcessed == "ㄍ" { strProcessed = "e" } + if strProcessed == "ㄑ" { strProcessed = "f" } + if strProcessed == "ㄕ" { strProcessed = "g" } + if strProcessed == "ㄘ" { strProcessed = "h" } + if strProcessed == "ㄛ" { strProcessed = "i" } + if strProcessed == "ㄨ" { strProcessed = "j" } + if strProcessed == "ㄜ" { strProcessed = "k" } + if strProcessed == "ㄠ" { strProcessed = "l" } + if strProcessed == "ㄩ" { strProcessed = "m" } + if strProcessed == "ㄙ" { strProcessed = "n" } + if strProcessed == "ㄟ" { strProcessed = "o" } + if strProcessed == "ㄣ" { strProcessed = "p" } + if strProcessed == "ㄆ" { strProcessed = "q" } + if strProcessed == "ㄐ" { strProcessed = "r" } + if strProcessed == "ㄋ" { strProcessed = "s" } + if strProcessed == "ㄔ" { strProcessed = "t" } + if strProcessed == "ㄧ" { strProcessed = "u" } + if strProcessed == "ㄒ" { strProcessed = "v" } + if strProcessed == "ㄊ" { strProcessed = "w" } + if strProcessed == "ㄌ" { strProcessed = "x" } + if strProcessed == "ㄗ" { strProcessed = "y" } + if strProcessed == "ㄈ" { strProcessed = "z" } + // 除了數字鍵區以外的標點符號。 + if strProcessed == "、" { strProcessed = "\\" } + if strProcessed == "「" { strProcessed = "[" } + if strProcessed == "」" { strProcessed = "]" } + if strProcessed == "『" { strProcessed = "{" } + if strProcessed == "』" { strProcessed = "}" } + if strProcessed == "," { strProcessed = "<" } + if strProcessed == "。" { strProcessed = ">" } + // 摁了 SHIFT 之後的數字區的符號。 + if strProcessed == "!" { strProcessed = "!" } + if strProcessed == "@" { strProcessed = "@" } + if strProcessed == "#" { strProcessed = "#" } + if strProcessed == "$" { strProcessed = "$" } + if strProcessed == "%" { strProcessed = "%" } + if strProcessed == "︿" { strProcessed = "^" } + if strProcessed == "&" { strProcessed = "&" } + if strProcessed == "*" { strProcessed = "*" } + if strProcessed == "(" { strProcessed = "(" } + if strProcessed == ")" { strProcessed = ")" } + // 摁了 Alt 的符號。 + if strProcessed == "—" { strProcessed = "-" } + // Apple 倚天注音佈局追加符號糾正項目。 + if mgrPrefs.basisKeyboardLayout == "com.apple.keylayout.ZhuyinEten" { + if strProcessed == "_" { strProcessed = "_" } + if strProcessed == ":" { strProcessed = ":" } + if strProcessed == "?" { strProcessed = "?" } + if strProcessed == "+" { strProcessed = "+" } + if strProcessed == "|" { strProcessed = "|" } + } + } + return strProcessed + } } diff --git a/Source/Modules/ControllerModules/InputState.swift b/Source/Modules/ControllerModules/InputState.swift index a2ea1f99..2d159521 100644 --- a/Source/Modules/ControllerModules/InputState.swift +++ b/Source/Modules/ControllerModules/InputState.swift @@ -1,20 +1,27 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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 @@ -52,359 +59,424 @@ import Cocoa /// one among the candidates. class InputState: NSObject { - /// Represents that the input controller is deactivated. - @objc (InputStateDeactivated) - class Deactivated: InputState { - override var description: String { - "" - } - } + /// Represents that the input controller is deactivated. + @objc(InputStateDeactivated) + class Deactivated: InputState { + override var description: String { + "" + } + } - // MARK: - + // MARK: - - /// Represents that the composing buffer is empty. - @objc (InputStateEmpty) - class Empty: InputState { - @objc var composingBuffer: String { - "" - } + /// Represents that the composing buffer is empty. + @objc(InputStateEmpty) + class Empty: InputState { + @objc var composingBuffer: String { + "" + } - override var description: String { - "" - } - } + override var description: String { + "" + } + } - // MARK: - + // MARK: - - /// Represents that the composing buffer is empty. - @objc (InputStateEmptyIgnoringPreviousState) - class EmptyIgnoringPreviousState: InputState { - @objc var composingBuffer: String { - "" - } - override var description: String { - "" - } - } + /// Represents that the composing buffer is empty. + @objc(InputStateEmptyIgnoringPreviousState) + class EmptyIgnoringPreviousState: InputState { + @objc var composingBuffer: String { + "" + } + override var description: String { + "" + } + } - // MARK: - + // MARK: - - /// Represents that the input controller is committing text into client app. - @objc (InputStateCommitting) - class Committing: InputState { - @objc private(set) var poppedText: String = "" + /// Represents that the input controller is committing text into client app. + @objc(InputStateCommitting) + class Committing: InputState { + @objc private(set) var poppedText: String = "" - @objc convenience init(poppedText: String) { - self.init() - self.poppedText = poppedText - } + @objc convenience init(poppedText: String) { + self.init() + self.poppedText = poppedText + } - override var description: String { - "" - } - } + override var description: String { + "" + } + } - // MARK: - + // MARK: - - /// Represents that the composing buffer is not empty. - @objc (InputStateNotEmpty) - class NotEmpty: InputState { - @objc private(set) var composingBuffer: String - @objc private(set) var cursorIndex: UInt + /// Represents that the composing buffer is not empty. + @objc(InputStateNotEmpty) + class NotEmpty: InputState { + @objc private(set) var composingBuffer: String + @objc private(set) var cursorIndex: UInt - @objc init(composingBuffer: String, cursorIndex: UInt) { - self.composingBuffer = composingBuffer - self.cursorIndex = cursorIndex - } + @objc init(composingBuffer: String, cursorIndex: UInt) { + self.composingBuffer = composingBuffer + self.cursorIndex = cursorIndex + } - override var description: String { - "" - } - } + override var description: String { + "" + } + } - // MARK: - + // MARK: - - /// Represents that the user is inputting text. - @objc (InputStateInputting) - class Inputting: NotEmpty { - @objc var poppedText: String = "" - @objc var tooltip: String = "" + /// Represents that the user is inputting text. + @objc(InputStateInputting) + class Inputting: NotEmpty { + @objc var poppedText: String = "" + @objc var tooltip: String = "" - @objc override init(composingBuffer: String, cursorIndex: UInt) { - super.init(composingBuffer: composingBuffer, cursorIndex: cursorIndex) - } + @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 - } + @objc var attributedString: NSAttributedString { + let attributedSting = NSAttributedString( + string: composingBuffer, + attributes: [ + .underlineStyle: NSUnderlineStyle.single.rawValue, + .markedClauseSegment: 0, + ]) + return attributedSting + } - override var description: String { - ", poppedText:\(poppedText)>" - } - } + override var description: String { + ", poppedText:\(poppedText)>" + } + } - // MARK: - + // MARK: - - private let kMinMarkRangeLength = 2 - private let kMaxMarkRangeLength = mgrPrefs.maxCandidateLength + private let kMinMarkRangeLength = 2 + private let kMaxMarkRangeLength = mgrPrefs.maxCandidateLength - /// Represents that the user is marking a range in the composing buffer. - @objc (InputStateMarking) - class Marking: NotEmpty { + /// 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 private var deleteTargetExists = false - @objc var tooltip: String { + @objc private(set) var markerIndex: UInt + @objc private(set) var markedRange: NSRange + @objc private var deleteTargetExists = false + @objc var tooltip: String { - if composingBuffer.count != readings.count { - TooltipController.backgroundColor = NSColor(red: 0.55, green: 0.00, blue: 0.00, alpha: 1.00) - TooltipController.textColor = NSColor.white - return NSLocalizedString("⚠︎ Unhandlable: Chars and Readings in buffer doesn't match.", comment: "") - } + if composingBuffer.count != readings.count { + TooltipController.backgroundColor = NSColor( + red: 0.55, green: 0.00, blue: 0.00, alpha: 1.00) + TooltipController.textColor = NSColor.white + return NSLocalizedString( + "⚠︎ Unhandlable: Chars and Readings in buffer doesn't match.", comment: "") + } - if mgrPrefs.phraseReplacementEnabled { - TooltipController.backgroundColor = NSColor.purple - TooltipController.textColor = NSColor.white - return NSLocalizedString("⚠︎ Phrase replacement mode enabled, interfering user phrase entry.", comment: "") - } - if markedRange.length == 0 { - return "" - } + if mgrPrefs.phraseReplacementEnabled { + TooltipController.backgroundColor = NSColor.purple + TooltipController.textColor = NSColor.white + return NSLocalizedString( + "⚠︎ Phrase replacement mode enabled, interfering user phrase entry.", comment: "" + ) + } + if markedRange.length == 0 { + return "" + } - let text = (composingBuffer as NSString).substring(with: markedRange) - if markedRange.length < kMinMarkRangeLength { - TooltipController.backgroundColor = NSColor(red: 0.18, green: 0.18, blue: 0.18, alpha: 1.00) - TooltipController.textColor = NSColor(red: 0.86, green: 0.86, blue: 0.86, alpha: 1.00) - return String(format: NSLocalizedString("\"%@\" length must ≥ 2 for a user phrase.", comment: ""), text) - } else if (markedRange.length > kMaxMarkRangeLength) { - TooltipController.backgroundColor = NSColor(red: 0.26, green: 0.16, blue: 0.00, alpha: 1.00) - TooltipController.textColor = NSColor(red: 1.00, green: 0.60, blue: 0.00, alpha: 1.00) - return String(format: NSLocalizedString("\"%@\" length should ≤ %d for a user phrase.", comment: ""), text, kMaxMarkRangeLength) - } + let text = (composingBuffer as NSString).substring(with: markedRange) + if markedRange.length < kMinMarkRangeLength { + TooltipController.backgroundColor = NSColor( + red: 0.18, green: 0.18, blue: 0.18, alpha: 1.00) + TooltipController.textColor = NSColor( + red: 0.86, green: 0.86, blue: 0.86, alpha: 1.00) + return String( + format: NSLocalizedString( + "\"%@\" length must ≥ 2 for a user phrase.", comment: ""), text) + } else if markedRange.length > kMaxMarkRangeLength { + TooltipController.backgroundColor = NSColor( + red: 0.26, green: 0.16, blue: 0.00, alpha: 1.00) + TooltipController.textColor = NSColor( + red: 1.00, green: 0.60, blue: 0.00, alpha: 1.00) + return String( + format: NSLocalizedString( + "\"%@\" length should ≤ %d for a user phrase.", comment: ""), + text, kMaxMarkRangeLength) + } - let (exactBegin, _) = (composingBuffer as NSString).characterIndex(from: markedRange.location) - let (exactEnd, _) = (composingBuffer as NSString).characterIndex(from: markedRange.location + markedRange.length) - let selectedReadings = readings[exactBegin.." - } + override var description: String { + "" + } - @objc func convertToInputting() -> Inputting { - let state = Inputting(composingBuffer: composingBuffer, cursorIndex: cursorIndex) - state.tooltip = tooltipForInputting - return state - } + @objc func convertToInputting() -> Inputting { + let state = Inputting(composingBuffer: composingBuffer, cursorIndex: cursorIndex) + state.tooltip = tooltipForInputting + return state + } - @objc var validToWrite: Bool { - /// vChewing allows users to input a string whose length differs - /// from the amount of Bopomofo readings. In this case, the range - /// in the composing buffer and the readings could not match, so - /// we disable the function to write user phrases in this case. - if composingBuffer.count != readings.count { - return false - } - if markedRange.length < kMinMarkRangeLength { - return false - } - if markedRange.length > kMaxMarkRangeLength { - return false - } - if ctlInputMethod.areWeDeleting && !deleteTargetExists { - return false - } - return markedRange.length >= kMinMarkRangeLength && markedRange.length <= kMaxMarkRangeLength - } + @objc var validToWrite: Bool { + /// vChewing allows users to input a string whose length differs + /// from the amount of Bopomofo readings. In this case, the range + /// in the composing buffer and the readings could not match, so + /// we disable the function to write user phrases in this case. + if composingBuffer.count != readings.count { + return false + } + if markedRange.length < kMinMarkRangeLength { + return false + } + if markedRange.length > kMaxMarkRangeLength { + return false + } + if ctlInputMethod.areWeDeleting && !deleteTargetExists { + return false + } + return markedRange.length >= kMinMarkRangeLength + && markedRange.length <= kMaxMarkRangeLength + } - @objc var chkIfUserPhraseExists: Bool { - let text = (composingBuffer as NSString).substring(with: markedRange) - let (exactBegin, _) = (composingBuffer as NSString).characterIndex(from: markedRange.location) - let (exactEnd, _) = (composingBuffer as NSString).characterIndex(from: markedRange.location + markedRange.length) - let selectedReadings = readings[exactBegin.." - } - } + override var description: String { + "" + } + } - // MARK: - + // MARK: - - /// Represents that the user is choosing in a candidates list - /// in the associated phrases mode. - @objc (InputStateAssociatedPhrases) - class AssociatedPhrases: InputState { - @objc private(set) var candidates: [String] = [] - @objc private(set) var useVerticalMode: Bool = false - @objc init(candidates: [String], useVerticalMode: Bool) { - self.candidates = candidates - self.useVerticalMode = useVerticalMode - super.init() - } + /// Represents that the user is choosing in a candidates list + /// in the associated phrases mode. + @objc(InputStateAssociatedPhrases) + class AssociatedPhrases: InputState { + @objc private(set) var candidates: [String] = [] + @objc private(set) var useVerticalMode: Bool = false + @objc init(candidates: [String], useVerticalMode: Bool) { + self.candidates = candidates + self.useVerticalMode = useVerticalMode + super.init() + } - override var description: String { - "" - } - } + override var description: String { + "" + } + } - @objc (InputStateSymbolTable) - class SymbolTable: ChoosingCandidate { - @objc var node: SymbolNode + @objc(InputStateSymbolTable) + class SymbolTable: ChoosingCandidate { + @objc var node: SymbolNode - @objc init(node: SymbolNode, useVerticalMode: Bool) { - self.node = node - let candidates = node.children?.map { $0.title } ?? [String]() - super.init(composingBuffer: "", cursorIndex: 0, candidates: candidates, useVerticalMode: useVerticalMode) - } + @objc init(node: SymbolNode, useVerticalMode: Bool) { + self.node = node + let candidates = node.children?.map { $0.title } ?? [String]() + super.init( + composingBuffer: "", cursorIndex: 0, candidates: candidates, + useVerticalMode: useVerticalMode) + } - override var description: String { - "" - } - } + override var description: String { + "" + } + } } @objc class SymbolNode: NSObject { - @objc var title: String - @objc var children: [SymbolNode]? + @objc var title: String + @objc var children: [SymbolNode]? - @objc init(_ title: String, _ children: [SymbolNode]? = nil) { - self.title = title - self.children = children - super.init() - } + @objc init(_ title: String, _ children: [SymbolNode]? = nil) { + self.title = title + self.children = children + super.init() + } - @objc init(_ title: String, symbols: String) { - self.title = title - self.children = Array(symbols).map { SymbolNode(String($0), nil) } - super.init() - } + @objc init(_ title: String, symbols: String) { + self.title = title + self.children = Array(symbols).map { SymbolNode(String($0), nil) } + super.init() + } - @objc static let catCommonSymbols = String(format: NSLocalizedString("catCommonSymbols", comment: "")) - @objc static let catHoriBrackets = String(format: NSLocalizedString("catHoriBrackets", comment: "")) - @objc static let catVertBrackets = String(format: NSLocalizedString("catVertBrackets", comment: "")) - @objc static let catGreekLetters = String(format: NSLocalizedString("catGreekLetters", comment: "")) - @objc static let catMathSymbols = String(format: NSLocalizedString("catMathSymbols", comment: "")) - @objc static let catCurrencyUnits = String(format: NSLocalizedString("catCurrencyUnits", comment: "")) - @objc static let catSpecialSymbols = String(format: NSLocalizedString("catSpecialSymbols", comment: "")) - @objc static let catUnicodeSymbols = String(format: NSLocalizedString("catUnicodeSymbols", comment: "")) - @objc static let catCircledKanjis = String(format: NSLocalizedString("catCircledKanjis", comment: "")) - @objc static let catCircledKataKana = String(format: NSLocalizedString("catCircledKataKana", comment: "")) - @objc static let catBracketKanjis = String(format: NSLocalizedString("catBracketKanjis", comment: "")) - @objc static let catSingleTableLines = String(format: NSLocalizedString("catSingleTableLines", comment: "")) - @objc static let catDoubleTableLines = String(format: NSLocalizedString("catDoubleTableLines", comment: "")) - @objc static let catFillingBlocks = String(format: NSLocalizedString("catFillingBlocks", comment: "")) - @objc static let catLineSegments = String(format: NSLocalizedString("catLineSegments", comment: "")) - - @objc static let root: SymbolNode = SymbolNode("/", [ - SymbolNode("`"), - SymbolNode(catCommonSymbols, symbols:",、。.?!;:‧‥﹐﹒˙·‘’“”〝〞‵′〃~$%@&#*"), - SymbolNode(catHoriBrackets, symbols:"()「」〔〕{}〈〉『』《》【】﹙﹚﹝﹞﹛﹜"), - SymbolNode(catVertBrackets, symbols:"︵︶﹁﹂︹︺︷︸︿﹀﹃﹄︽︾︻︼"), - SymbolNode(catGreekLetters, symbols:"αβγδεζηθικλμνξοπρστυφχψωΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ"), - SymbolNode(catMathSymbols, symbols:"+-×÷=≠≒∞±√<>﹤﹥≦≧∩∪ˇ⊥∠∟⊿㏒㏑∫∮∵∴╳﹢"), - SymbolNode(catCurrencyUnits, symbols:"$€¥¢£₽₨₩฿₺₮₱₭₴₦৲৳૱௹﷼₹₲₪₡₫៛₵₢₸₤₳₥₠₣₰₧₯₶₷"), - SymbolNode(catSpecialSymbols, symbols:"↑↓←→↖↗↙↘↺⇧⇩⇦⇨⇄⇆⇅⇵↻◎○●⊕⊙※△▲☆★◇◆□■▽▼§¥〒¢£♀♂↯"), - SymbolNode(catUnicodeSymbols, symbols:"♨☀☁☂☃♠♥♣♦♩♪♫♬☺☻"), - SymbolNode(catCircledKanjis, symbols:"㊟㊞㊚㊛㊊㊋㊌㊍㊎㊏㊐㊑㊒㊓㊔㊕㊖㊗︎㊘㊙︎㊜㊝㊠㊡㊢㊣㊤㊥㊦㊧㊨㊩㊪㊫㊬㊭㊮㊯㊰🈚︎🈯︎"), - SymbolNode(catCircledKataKana, symbols:"㋐㋑㋒㋓㋔㋕㋖㋗㋘㋙㋚㋛㋜㋝㋞㋟㋠㋡㋢㋣㋤㋥㋦㋧㋨㋩㋪㋫㋬㋭㋮㋯㋰㋱㋲㋳㋴㋵㋶㋷㋸㋹㋺㋻㋼㋾"), - SymbolNode(catBracketKanjis, symbols:"㈪㈫㈬㈭㈮㈯㈰㈱㈲㈳㈴㈵㈶㈷㈸㈹㈺㈻㈼㈽㈾㈿㉀㉁㉂㉃"), - SymbolNode(catSingleTableLines, symbols:"├─┼┴┬┤┌┐╞═╪╡│▕└┘╭╮╰╯"), - SymbolNode(catDoubleTableLines, symbols:"╔╦╗╠═╬╣╓╥╖╒╤╕║╚╩╝╟╫╢╙╨╜╞╪╡╘╧╛"), - SymbolNode(catFillingBlocks, symbols:"_ˍ▁▂▃▄▅▆▇█▏▎▍▌▋▊▉◢◣◥◤"), - SymbolNode(catLineSegments, symbols:"﹣﹦≡|∣∥–︱—︳╴¯ ̄﹉﹊﹍﹎﹋﹌﹏︴∕﹨╱╲/\"), - ]) + @objc static let catCommonSymbols = String( + format: NSLocalizedString("catCommonSymbols", comment: "")) + @objc static let catHoriBrackets = String( + format: NSLocalizedString("catHoriBrackets", comment: "")) + @objc static let catVertBrackets = String( + format: NSLocalizedString("catVertBrackets", comment: "")) + @objc static let catGreekLetters = String( + format: NSLocalizedString("catGreekLetters", comment: "")) + @objc static let catMathSymbols = String( + format: NSLocalizedString("catMathSymbols", comment: "")) + @objc static let catCurrencyUnits = String( + format: NSLocalizedString("catCurrencyUnits", comment: "")) + @objc static let catSpecialSymbols = String( + format: NSLocalizedString("catSpecialSymbols", comment: "")) + @objc static let catUnicodeSymbols = String( + format: NSLocalizedString("catUnicodeSymbols", comment: "")) + @objc static let catCircledKanjis = String( + format: NSLocalizedString("catCircledKanjis", comment: "")) + @objc static let catCircledKataKana = String( + format: NSLocalizedString("catCircledKataKana", comment: "")) + @objc static let catBracketKanjis = String( + format: NSLocalizedString("catBracketKanjis", comment: "")) + @objc static let catSingleTableLines = String( + format: NSLocalizedString("catSingleTableLines", comment: "")) + @objc static let catDoubleTableLines = String( + format: NSLocalizedString("catDoubleTableLines", comment: "")) + @objc static let catFillingBlocks = String( + format: NSLocalizedString("catFillingBlocks", comment: "")) + @objc static let catLineSegments = String( + format: NSLocalizedString("catLineSegments", comment: "")) + + @objc static let root: SymbolNode = SymbolNode( + "/", + [ + SymbolNode("`"), + SymbolNode(catCommonSymbols, symbols: ",、。.?!;:‧‥﹐﹒˙·‘’“”〝〞‵′〃~$%@&#*"), + SymbolNode(catHoriBrackets, symbols: "()「」〔〕{}〈〉『』《》【】﹙﹚﹝﹞﹛﹜"), + SymbolNode(catVertBrackets, symbols: "︵︶﹁﹂︹︺︷︸︿﹀﹃﹄︽︾︻︼"), + SymbolNode( + catGreekLetters, symbols: "αβγδεζηθικλμνξοπρστυφχψωΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ"), + SymbolNode(catMathSymbols, symbols: "+-×÷=≠≒∞±√<>﹤﹥≦≧∩∪ˇ⊥∠∟⊿㏒㏑∫∮∵∴╳﹢"), + SymbolNode(catCurrencyUnits, symbols: "$€¥¢£₽₨₩฿₺₮₱₭₴₦৲৳૱௹﷼₹₲₪₡₫៛₵₢₸₤₳₥₠₣₰₧₯₶₷"), + SymbolNode(catSpecialSymbols, symbols: "↑↓←→↖↗↙↘↺⇧⇩⇦⇨⇄⇆⇅⇵↻◎○●⊕⊙※△▲☆★◇◆□■▽▼§¥〒¢£♀♂↯"), + SymbolNode(catUnicodeSymbols, symbols: "♨☀☁☂☃♠♥♣♦♩♪♫♬☺☻"), + SymbolNode(catCircledKanjis, symbols: "㊟㊞㊚㊛㊊㊋㊌㊍㊎㊏㊐㊑㊒㊓㊔㊕㊖㊗︎㊘㊙︎㊜㊝㊠㊡㊢㊣㊤㊥㊦㊧㊨㊩㊪㊫㊬㊭㊮㊯㊰🈚︎🈯︎"), + SymbolNode( + catCircledKataKana, symbols: "㋐㋑㋒㋓㋔㋕㋖㋗㋘㋙㋚㋛㋜㋝㋞㋟㋠㋡㋢㋣㋤㋥㋦㋧㋨㋩㋪㋫㋬㋭㋮㋯㋰㋱㋲㋳㋴㋵㋶㋷㋸㋹㋺㋻㋼㋾"), + SymbolNode(catBracketKanjis, symbols: "㈪㈫㈬㈭㈮㈯㈰㈱㈲㈳㈴㈵㈶㈷㈸㈹㈺㈻㈼㈽㈾㈿㉀㉁㉂㉃"), + SymbolNode(catSingleTableLines, symbols: "├─┼┴┬┤┌┐╞═╪╡│▕└┘╭╮╰╯"), + SymbolNode(catDoubleTableLines, symbols: "╔╦╗╠═╬╣╓╥╖╒╤╕║╚╩╝╟╫╢╙╨╜╞╪╡╘╧╛"), + SymbolNode(catFillingBlocks, symbols: "_ˍ▁▂▃▄▅▆▇█▏▎▍▌▋▊▉◢◣◥◤"), + SymbolNode(catLineSegments, symbols: "﹣﹦≡|∣∥–︱—︳╴¯ ̄﹉﹊﹍﹎﹋﹌﹏︴∕﹨╱╲/\"), + ]) } diff --git a/Source/Modules/ControllerModules/KeyParser.swift b/Source/Modules/ControllerModules/KeyParser.swift index 36054d5e..1060b474 100644 --- a/Source/Modules/ControllerModules/KeyParser.swift +++ b/Source/Modules/ControllerModules/KeyParser.swift @@ -1,20 +1,27 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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 @@ -22,254 +29,264 @@ import Cocoa // Use KeyCodes as much as possible since its recognition won't be affected by macOS Base Keyboard Layouts. // KeyCodes: https://eastmanreference.com/complete-list-of-applescript-key-codes @objc enum KeyCode: UInt16 { - case none = 0 - case space = 49 - case backSpace = 51 - case esc = 53 - case tab = 48 - case enterLF = 76 - case enterCR = 36 - case up = 126 - case down = 125 - case left = 123 - case right = 124 - case pageUp = 116 - case pageDown = 121 - case home = 115 - case end = 119 - case delete = 117 - case leftShift = 56 - case rightShift = 60 - case capsLock = 57 - case symbolMenuPhysicalKey = 50 + case none = 0 + case space = 49 + case backSpace = 51 + case esc = 53 + case tab = 48 + case enterLF = 76 + case enterCR = 36 + case up = 126 + case down = 125 + case left = 123 + case right = 124 + case pageUp = 116 + case pageDown = 121 + case home = 115 + case end = 119 + case delete = 117 + case leftShift = 56 + case rightShift = 60 + case capsLock = 57 + case symbolMenuPhysicalKey = 50 } // CharCodes: https://theasciicode.com.ar/ascii-control-characters/horizontal-tab-ascii-code-9.html -enum CharCode: UInt/*16*/ { - case yajuusenpai = 1145141919810893 - // - CharCode is not reliable at all. KeyCode is the most accurate. KeyCode doesn't give a phuque about the character sent through macOS keyboard layouts but only focuses on which physical key is pressed. +enum CharCode: UInt /*16*/ { + case yajuusenpai = 1_145_141_919_810_893 + // - CharCode is not reliable at all. KeyCode is the most accurate. KeyCode doesn't give a phuque about the character sent through macOS keyboard layouts but only focuses on which physical key is pressed. } class keyParser: NSObject { - @objc private (set) var useVerticalMode: Bool - @objc private (set) var inputText: String? - @objc private (set) var inputTextIgnoringModifiers: String? - @objc private (set) var charCode: UInt16 - @objc private (set) var keyCode: UInt16 - private var isFlagChanged: Bool - private var flags: NSEvent.ModifierFlags - private var cursorForwardKey: KeyCode - private var cursorBackwardKey: KeyCode - private var extraChooseCandidateKey: KeyCode - private var extraChooseCandidateKeyReverse: KeyCode - private var absorbedArrowKey: KeyCode - private var verticalModeOnlyChooseCandidateKey: KeyCode - @objc private (set) var emacsKey: vChewingEmacsKey + @objc private(set) var useVerticalMode: Bool + @objc private(set) var inputText: String? + @objc private(set) var inputTextIgnoringModifiers: String? + @objc private(set) var charCode: UInt16 + @objc private(set) var keyCode: UInt16 + private var isFlagChanged: Bool + private var flags: NSEvent.ModifierFlags + private var cursorForwardKey: KeyCode + private var cursorBackwardKey: KeyCode + private var extraChooseCandidateKey: KeyCode + private var extraChooseCandidateKeyReverse: KeyCode + private var absorbedArrowKey: KeyCode + private var verticalModeOnlyChooseCandidateKey: KeyCode + @objc private(set) var emacsKey: vChewingEmacsKey - @objc init(inputText: String?, keyCode: UInt16, charCode: UInt16, flags: NSEvent.ModifierFlags, isVerticalMode: Bool, inputTextIgnoringModifiers: String? = nil) { - let inputText = AppleKeyboardConverter.cnvStringApple2ABC(inputText ?? "") - let inputTextIgnoringModifiers = AppleKeyboardConverter.cnvStringApple2ABC(inputTextIgnoringModifiers ?? inputText) - self.inputText = inputText - self.inputTextIgnoringModifiers = inputTextIgnoringModifiers - self.keyCode = keyCode - self.charCode = AppleKeyboardConverter.cnvApple2ABC(charCode) - self.flags = flags - self.isFlagChanged = false - useVerticalMode = isVerticalMode - emacsKey = EmacsKeyHelper.detect(charCode: AppleKeyboardConverter.cnvApple2ABC(charCode), flags: flags) - cursorForwardKey = useVerticalMode ? .down : .right - cursorBackwardKey = useVerticalMode ? .up : .left - extraChooseCandidateKey = useVerticalMode ? .left : .down - extraChooseCandidateKeyReverse = useVerticalMode ? .right : .up - absorbedArrowKey = useVerticalMode ? .right : .up - verticalModeOnlyChooseCandidateKey = useVerticalMode ? absorbedArrowKey : .none - super.init() - } + @objc init( + inputText: String?, keyCode: UInt16, charCode: UInt16, flags: NSEvent.ModifierFlags, + isVerticalMode: Bool, inputTextIgnoringModifiers: String? = nil + ) { + let inputText = AppleKeyboardConverter.cnvStringApple2ABC(inputText ?? "") + let inputTextIgnoringModifiers = AppleKeyboardConverter.cnvStringApple2ABC( + inputTextIgnoringModifiers ?? inputText) + self.inputText = inputText + self.inputTextIgnoringModifiers = inputTextIgnoringModifiers + self.keyCode = keyCode + self.charCode = AppleKeyboardConverter.cnvApple2ABC(charCode) + self.flags = flags + self.isFlagChanged = false + useVerticalMode = isVerticalMode + emacsKey = EmacsKeyHelper.detect( + charCode: AppleKeyboardConverter.cnvApple2ABC(charCode), flags: flags) + cursorForwardKey = useVerticalMode ? .down : .right + cursorBackwardKey = useVerticalMode ? .up : .left + extraChooseCandidateKey = useVerticalMode ? .left : .down + extraChooseCandidateKeyReverse = useVerticalMode ? .right : .up + absorbedArrowKey = useVerticalMode ? .right : .up + verticalModeOnlyChooseCandidateKey = useVerticalMode ? absorbedArrowKey : .none + super.init() + } - @objc init(event: NSEvent, isVerticalMode: Bool) { - inputText = AppleKeyboardConverter.cnvStringApple2ABC(event.characters ?? "") - inputTextIgnoringModifiers = AppleKeyboardConverter.cnvStringApple2ABC(event.charactersIgnoringModifiers ?? "") - keyCode = event.keyCode - flags = event.modifierFlags - isFlagChanged = (event.type == .flagsChanged) ? true : false - useVerticalMode = isVerticalMode - let charCode: UInt16 = { - guard let inputText = event.characters, inputText.count > 0 else { - return 0 - } - let first = inputText[inputText.startIndex].utf16.first! - return first - }() - self.charCode = AppleKeyboardConverter.cnvApple2ABC(charCode) - emacsKey = EmacsKeyHelper.detect(charCode: AppleKeyboardConverter.cnvApple2ABC(charCode), flags: event.modifierFlags) - cursorForwardKey = useVerticalMode ? .down : .right - cursorBackwardKey = useVerticalMode ? .up : .left - extraChooseCandidateKey = useVerticalMode ? .left : .down - extraChooseCandidateKeyReverse = useVerticalMode ? .right : .up - absorbedArrowKey = useVerticalMode ? .right : .up - verticalModeOnlyChooseCandidateKey = useVerticalMode ? absorbedArrowKey : .none - super.init() - } + @objc init(event: NSEvent, isVerticalMode: Bool) { + inputText = AppleKeyboardConverter.cnvStringApple2ABC(event.characters ?? "") + inputTextIgnoringModifiers = AppleKeyboardConverter.cnvStringApple2ABC( + event.charactersIgnoringModifiers ?? "") + keyCode = event.keyCode + flags = event.modifierFlags + isFlagChanged = (event.type == .flagsChanged) ? true : false + useVerticalMode = isVerticalMode + let charCode: UInt16 = { + guard let inputText = event.characters, inputText.count > 0 else { + return 0 + } + let first = inputText[inputText.startIndex].utf16.first! + return first + }() + self.charCode = AppleKeyboardConverter.cnvApple2ABC(charCode) + emacsKey = EmacsKeyHelper.detect( + charCode: AppleKeyboardConverter.cnvApple2ABC(charCode), flags: event.modifierFlags) + cursorForwardKey = useVerticalMode ? .down : .right + cursorBackwardKey = useVerticalMode ? .up : .left + extraChooseCandidateKey = useVerticalMode ? .left : .down + extraChooseCandidateKeyReverse = useVerticalMode ? .right : .up + absorbedArrowKey = useVerticalMode ? .right : .up + verticalModeOnlyChooseCandidateKey = useVerticalMode ? absorbedArrowKey : .none + super.init() + } - override var description: String { - charCode = AppleKeyboardConverter.cnvApple2ABC(charCode) - inputText = AppleKeyboardConverter.cnvStringApple2ABC(inputText ?? "") - inputTextIgnoringModifiers = AppleKeyboardConverter.cnvStringApple2ABC(inputTextIgnoringModifiers ?? "") - return "<\(super.description) inputText:\(String(describing: inputText)), inputTextIgnoringModifiers:\(String(describing: inputTextIgnoringModifiers)) charCode:\(charCode), keyCode:\(keyCode), flags:\(flags), cursorForwardKey:\(cursorForwardKey), cursorBackwardKey:\(cursorBackwardKey), extraChooseCandidateKey:\(extraChooseCandidateKey), extraChooseCandidateKeyReverse:\(extraChooseCandidateKeyReverse), absorbedArrowKey:\(absorbedArrowKey), verticalModeOnlyChooseCandidateKey:\(verticalModeOnlyChooseCandidateKey), emacsKey:\(emacsKey), useVerticalMode:\(useVerticalMode)>" - } + override var description: String { + charCode = AppleKeyboardConverter.cnvApple2ABC(charCode) + inputText = AppleKeyboardConverter.cnvStringApple2ABC(inputText ?? "") + inputTextIgnoringModifiers = AppleKeyboardConverter.cnvStringApple2ABC( + inputTextIgnoringModifiers ?? "") + return + "<\(super.description) inputText:\(String(describing: inputText)), inputTextIgnoringModifiers:\(String(describing: inputTextIgnoringModifiers)) charCode:\(charCode), keyCode:\(keyCode), flags:\(flags), cursorForwardKey:\(cursorForwardKey), cursorBackwardKey:\(cursorBackwardKey), extraChooseCandidateKey:\(extraChooseCandidateKey), extraChooseCandidateKeyReverse:\(extraChooseCandidateKeyReverse), absorbedArrowKey:\(absorbedArrowKey), verticalModeOnlyChooseCandidateKey:\(verticalModeOnlyChooseCandidateKey), emacsKey:\(emacsKey), useVerticalMode:\(useVerticalMode)>" + } - @objc var isShiftHold: Bool { - flags.contains([.shift]) - } + @objc var isShiftHold: Bool { + flags.contains([.shift]) + } - @objc var isCommandHold: Bool { - flags.contains([.command]) - } + @objc var isCommandHold: Bool { + flags.contains([.command]) + } - @objc var isControlHold: Bool { - flags.contains([.control]) - } + @objc var isControlHold: Bool { + flags.contains([.control]) + } - @objc var isControlHotKey: Bool { - flags.contains([.control]) && inputText?.first?.isLetter ?? false - } + @objc var isControlHotKey: Bool { + flags.contains([.control]) && inputText?.first?.isLetter ?? false + } - @objc var isOptionHotKey: Bool { - flags.contains([.option]) && inputText?.first?.isLetter ?? false - } + @objc var isOptionHotKey: Bool { + flags.contains([.option]) && inputText?.first?.isLetter ?? false + } - @objc var isOptionHold: Bool { - flags.contains([.option]) - } + @objc var isOptionHold: Bool { + flags.contains([.option]) + } - @objc var isCapsLockOn: Bool { - flags.contains([.capsLock]) - } + @objc var isCapsLockOn: Bool { + flags.contains([.capsLock]) + } - @objc var isNumericPad: Bool { - flags.contains([.numericPad]) - } + @objc var isNumericPad: Bool { + flags.contains([.numericPad]) + } - @objc var isFunctionKeyHold: Bool { - flags.contains([.function]) - } + @objc var isFunctionKeyHold: Bool { + flags.contains([.function]) + } - @objc var isReservedKey: Bool { - guard let code = KeyCode(rawValue: keyCode) else { - return false - } - return code.rawValue != KeyCode.none.rawValue - } + @objc var isReservedKey: Bool { + guard let code = KeyCode(rawValue: keyCode) else { + return false + } + return code.rawValue != KeyCode.none.rawValue + } - @objc var isTab: Bool { - KeyCode(rawValue: keyCode) == KeyCode.tab - } + @objc var isTab: Bool { + KeyCode(rawValue: keyCode) == KeyCode.tab + } - @objc var isEnter: Bool { - (KeyCode(rawValue: keyCode) == KeyCode.enterCR) || (KeyCode(rawValue: keyCode) == KeyCode.enterLF) - } + @objc var isEnter: Bool { + (KeyCode(rawValue: keyCode) == KeyCode.enterCR) + || (KeyCode(rawValue: keyCode) == KeyCode.enterLF) + } - @objc var isUp: Bool { - KeyCode(rawValue: keyCode) == KeyCode.up - } + @objc var isUp: Bool { + KeyCode(rawValue: keyCode) == KeyCode.up + } - @objc var isDown: Bool { - KeyCode(rawValue: keyCode) == KeyCode.down - } + @objc var isDown: Bool { + KeyCode(rawValue: keyCode) == KeyCode.down + } - @objc var isLeft: Bool { - KeyCode(rawValue: keyCode) == KeyCode.left - } + @objc var isLeft: Bool { + KeyCode(rawValue: keyCode) == KeyCode.left + } - @objc var isRight: Bool { - KeyCode(rawValue: keyCode) == KeyCode.right - } + @objc var isRight: Bool { + KeyCode(rawValue: keyCode) == KeyCode.right + } - @objc var isPageUp: Bool { - KeyCode(rawValue: keyCode) == KeyCode.pageUp - } + @objc var isPageUp: Bool { + KeyCode(rawValue: keyCode) == KeyCode.pageUp + } - @objc var isPageDown: Bool { - KeyCode(rawValue: keyCode) == KeyCode.pageDown - } + @objc var isPageDown: Bool { + KeyCode(rawValue: keyCode) == KeyCode.pageDown + } - @objc var isSpace: Bool { - KeyCode(rawValue: keyCode) == KeyCode.space - } + @objc var isSpace: Bool { + KeyCode(rawValue: keyCode) == KeyCode.space + } - @objc var isBackSpace: Bool { - KeyCode(rawValue: keyCode) == KeyCode.backSpace - } + @objc var isBackSpace: Bool { + KeyCode(rawValue: keyCode) == KeyCode.backSpace + } - @objc var isESC: Bool { - KeyCode(rawValue: keyCode) == KeyCode.esc - } + @objc var isESC: Bool { + KeyCode(rawValue: keyCode) == KeyCode.esc + } - @objc var isHome: Bool { - KeyCode(rawValue: keyCode) == KeyCode.home - } + @objc var isHome: Bool { + KeyCode(rawValue: keyCode) == KeyCode.home + } - @objc var isEnd: Bool { - KeyCode(rawValue: keyCode) == KeyCode.end - } + @objc var isEnd: Bool { + KeyCode(rawValue: keyCode) == KeyCode.end + } - @objc var isDelete: Bool { - KeyCode(rawValue: keyCode) == KeyCode.delete - } + @objc var isDelete: Bool { + KeyCode(rawValue: keyCode) == KeyCode.delete + } - @objc var isCursorBackward: Bool { - KeyCode(rawValue: keyCode) == cursorBackwardKey - } + @objc var isCursorBackward: Bool { + KeyCode(rawValue: keyCode) == cursorBackwardKey + } - @objc var isCursorForward: Bool { - KeyCode(rawValue: keyCode) == cursorForwardKey - } + @objc var isCursorForward: Bool { + KeyCode(rawValue: keyCode) == cursorForwardKey + } - @objc var isAbsorbedArrowKey: Bool { - KeyCode(rawValue: keyCode) == absorbedArrowKey - } + @objc var isAbsorbedArrowKey: Bool { + KeyCode(rawValue: keyCode) == absorbedArrowKey + } - @objc var isExtraChooseCandidateKey: Bool { - KeyCode(rawValue: keyCode) == extraChooseCandidateKey - } + @objc var isExtraChooseCandidateKey: Bool { + KeyCode(rawValue: keyCode) == extraChooseCandidateKey + } - @objc var isExtraChooseCandidateKeyReverse: Bool { - KeyCode(rawValue: keyCode) == extraChooseCandidateKeyReverse - } + @objc var isExtraChooseCandidateKeyReverse: Bool { + KeyCode(rawValue: keyCode) == extraChooseCandidateKeyReverse + } - @objc var isVerticalModeOnlyChooseCandidateKey: Bool { - KeyCode(rawValue: keyCode) == verticalModeOnlyChooseCandidateKey - } + @objc var isVerticalModeOnlyChooseCandidateKey: Bool { + KeyCode(rawValue: keyCode) == verticalModeOnlyChooseCandidateKey + } - @objc var isUpperCaseASCIILetterKey: Bool { - // 這裡必須加上「flags == .shift」,否則會出現某些情況下輸入法「誤判當前鍵入的非 Shift 字符為大寫」的問題。 - self.charCode >= 65 && self.charCode <= 90 && flags == .shift - } + @objc var isUpperCaseASCIILetterKey: Bool { + // 這裡必須加上「flags == .shift」,否則會出現某些情況下輸入法「誤判當前鍵入的非 Shift 字符為大寫」的問題。 + self.charCode >= 65 && self.charCode <= 90 && flags == .shift + } - @objc var isSymbolMenuPhysicalKey: Bool { - // 這裡必須用 KeyCode,這樣才不會受隨 macOS 版本更動的 Apple 動態注音鍵盤排列內容的影響。 - // 只是必須得與 ![input isShift] 搭配使用才可以(也就是僅判定 Shift 沒被摁下的情形)。 - KeyCode(rawValue: keyCode) == KeyCode.symbolMenuPhysicalKey - } + @objc var isSymbolMenuPhysicalKey: Bool { + // 這裡必須用 KeyCode,這樣才不會受隨 macOS 版本更動的 Apple 動態注音鍵盤排列內容的影響。 + // 只是必須得與 ![input isShift] 搭配使用才可以(也就是僅判定 Shift 沒被摁下的情形)。 + KeyCode(rawValue: keyCode) == KeyCode.symbolMenuPhysicalKey + } } @objc enum vChewingEmacsKey: UInt16 { - case none = 0 - case forward = 6 // F - case backward = 2 // B - case home = 1 // A - case end = 5 // E - case delete = 4 // D - case nextPage = 22 // V + case none = 0 + case forward = 6 // F + case backward = 2 // B + case home = 1 // A + case end = 5 // E + case delete = 4 // D + case nextPage = 22 // V } class EmacsKeyHelper: NSObject { - @objc static func detect(charCode: UniChar, flags: NSEvent.ModifierFlags) -> vChewingEmacsKey { - let charCode = AppleKeyboardConverter.cnvApple2ABC(charCode) - if flags.contains(.control) { - return vChewingEmacsKey(rawValue: charCode) ?? .none - } - return .none; - } + @objc static func detect(charCode: UniChar, flags: NSEvent.ModifierFlags) -> vChewingEmacsKey { + let charCode = AppleKeyboardConverter.cnvApple2ABC(charCode) + if flags.contains(.control) { + return vChewingEmacsKey(rawValue: charCode) ?? .none + } + return .none + } } diff --git a/Source/Modules/ControllerModules/NSStringUtils.swift b/Source/Modules/ControllerModules/NSStringUtils.swift index 852d7422..20394f81 100644 --- a/Source/Modules/ControllerModules/NSStringUtils.swift +++ b/Source/Modules/ControllerModules/NSStringUtils.swift @@ -1,69 +1,76 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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 -public extension NSString { +extension NSString { - /// Converts the index in an NSString to the index in a Swift string. - /// - /// An Emoji might be compose by more than one UTF-16 code points, however - /// the length of an NSString is only the sum of the UTF-16 code points. It - /// causes that the NSString and Swift string representation of the same - /// string have different lengths once the string contains such Emoji. The - /// method helps to find the index in a Swift string by passing the index - /// in an NSString. - func characterIndex(from utf16Index:Int) -> (Int, String) { - let string = (self as String) - var length = 0 - for (i, character) in string.enumerated() { - length += character.utf16.count - if length > utf16Index { - return (i, string) - } - } - return (string.count, string) - } + /// Converts the index in an NSString to the index in a Swift string. + /// + /// An Emoji might be compose by more than one UTF-16 code points, however + /// the length of an NSString is only the sum of the UTF-16 code points. It + /// causes that the NSString and Swift string representation of the same + /// string have different lengths once the string contains such Emoji. The + /// method helps to find the index in a Swift string by passing the index + /// in an NSString. + public func characterIndex(from utf16Index: Int) -> (Int, String) { + let string = (self as String) + var length = 0 + for (i, character) in string.enumerated() { + length += character.utf16.count + if length > utf16Index { + return (i, string) + } + } + return (string.count, string) + } - @objc func nextUtf16Position(for index: Int) -> Int { - var (fixedIndex, string) = characterIndex(from: index) - if fixedIndex < string.count { - fixedIndex += 1 - } - return string[.. Int { + var (fixedIndex, string) = characterIndex(from: index) + if fixedIndex < string.count { + fixedIndex += 1 + } + return string[.. Int { - var (fixedIndex, string) = characterIndex(from: index) - if fixedIndex > 0 { - fixedIndex -= 1 - } - return string[.. Int { + var (fixedIndex, string) = characterIndex(from: index) + if fixedIndex > 0 { + fixedIndex -= 1 + } + return string[.. [NSString] { - Array(self as String).map { - NSString(string: String($0)) - } - } + @objc public func split() -> [NSString] { + Array(self as String).map { + NSString(string: String($0)) + } + } } diff --git a/Source/Modules/ControllerModules/vChewingKanjiConverter.swift b/Source/Modules/ControllerModules/vChewingKanjiConverter.swift index d1bb12f0..70572011 100644 --- a/Source/Modules/ControllerModules/vChewingKanjiConverter.swift +++ b/Source/Modules/ControllerModules/vChewingKanjiConverter.swift @@ -1,769 +1,775 @@ // Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ import Cocoa -private extension String { - mutating func selfReplace(_ strOf: String, _ strWith: String = "") { - self = self.replacingOccurrences(of: strOf, with: strWith) - } +extension String { + fileprivate mutating func selfReplace(_ strOf: String, _ strWith: String = "") { + self = self.replacingOccurrences(of: strOf, with: strWith) + } } @objc class vChewingKanjiConverter: NSObject { - @objc class func cnvTradToKangXi(_ strObj: String) -> String { - var strObj = strObj - strObj.selfReplace("偽", "僞") - strObj.selfReplace("啟", "啓") - strObj.selfReplace("吃", "喫") - strObj.selfReplace("嫻", "嫺") - strObj.selfReplace("媯", "嬀") - strObj.selfReplace("峰", "峯") - strObj.selfReplace("么", "幺") - strObj.selfReplace("抬", "擡") - strObj.selfReplace("稜", "棱") - strObj.selfReplace("簷", "檐") - strObj.selfReplace("汙", "污") - strObj.selfReplace("洩", "泄") - strObj.selfReplace("溈", "潙") - strObj.selfReplace("潀", "潨") - strObj.selfReplace("為", "爲") - strObj.selfReplace("床", "牀") - strObj.selfReplace("痺", "痹") - strObj.selfReplace("痴", "癡") - strObj.selfReplace("皂", "皁") - strObj.selfReplace("著", "着") - strObj.selfReplace("睪", "睾") - strObj.selfReplace("秘", "祕") - strObj.selfReplace("灶", "竈") - strObj.selfReplace("粽", "糉") - strObj.selfReplace("韁", "繮") - strObj.selfReplace("才", "纔") - strObj.selfReplace("群", "羣") - strObj.selfReplace("唇", "脣") - strObj.selfReplace("參", "蔘") - strObj.selfReplace("蒍", "蔿") - strObj.selfReplace("眾", "衆") - strObj.selfReplace("裡", "裏") - strObj.selfReplace("核", "覈") - strObj.selfReplace("踴", "踊") - strObj.selfReplace("缽", "鉢") - strObj.selfReplace("針", "鍼") - strObj.selfReplace("鯰", "鮎") - strObj.selfReplace("麵", "麪") - strObj.selfReplace("顎", "齶") - strObj.selfReplace("口喫", "口吃") - strObj.selfReplace("合着", "合著") - strObj.selfReplace("名着", "名著") - strObj.selfReplace("巨着", "巨著") - strObj.selfReplace("鉅着", "鉅著") - strObj.selfReplace("昭着", "昭著") - strObj.selfReplace("所着", "所著") - strObj.selfReplace("遺着", "遺著") - strObj.selfReplace("顯着", "顯著") - strObj.selfReplace("土着", "土著") - strObj.selfReplace("着作", "著作") - strObj.selfReplace("着名", "著名") - strObj.selfReplace("着式", "著式") - strObj.selfReplace("着志", "著志") - strObj.selfReplace("着於", "著於") - strObj.selfReplace("着書", "著書") - strObj.selfReplace("着白", "著白") - strObj.selfReplace("着稱", "著稱") - strObj.selfReplace("着者", "著者") - strObj.selfReplace("着述", "著述") - strObj.selfReplace("着錄", "著錄") - strObj.selfReplace("蹇喫", "蹇吃") - strObj.selfReplace("大着", "大著") - strObj.selfReplace("刊着", "刊著") - strObj.selfReplace("玄着", "玄著") - strObj.selfReplace("白着", "白著") - strObj.selfReplace("住着", "住著") - strObj.selfReplace("刻着", "刻著") - strObj.selfReplace("卓着", "卓著") - strObj.selfReplace("拙着", "拙著") - strObj.selfReplace("查着", "查著") - strObj.selfReplace("炳着", "炳著") - strObj.selfReplace("原着", "原著") - strObj.selfReplace("專着", "專著") - strObj.selfReplace("焯着", "焯著") - strObj.selfReplace("着論", "著論") - strObj.selfReplace("着績", "著績") - strObj.selfReplace("較着", "較著") - strObj.selfReplace("彰着", "彰著") - strObj.selfReplace("撰着", "撰著") - strObj.selfReplace("編着", "編著") - strObj.selfReplace("論着", "論著") - strObj.selfReplace("雜着", "雜著") - strObj.selfReplace("譯着", "譯著") - strObj.selfReplace("地覈", "地核") - strObj.selfReplace("多覈", "多核") - strObj.selfReplace("氘覈", "氘核") - strObj.selfReplace("杏覈", "杏核") - strObj.selfReplace("非覈", "非核") - strObj.selfReplace("覈三", "核三") - strObj.selfReplace("覈下", "核下") - strObj.selfReplace("覈災", "核災") - strObj.selfReplace("覈武", "核武") - strObj.selfReplace("覈狀", "核狀") - strObj.selfReplace("覈桃", "核桃") - strObj.selfReplace("覈彈", "核彈") - strObj.selfReplace("覈戰", "核戰") - strObj.selfReplace("覈糖", "核糖") - strObj.selfReplace("覈醣", "核醣") - strObj.selfReplace("晶覈", "晶核") - strObj.selfReplace("熱覈", "熱核") - strObj.selfReplace("反覈", "反核") - strObj.selfReplace("卵覈", "卵核") - strObj.selfReplace("果覈", "果核") - strObj.selfReplace("剋覈", "剋核") - strObj.selfReplace("覈力", "核力") - strObj.selfReplace("覈子", "核子") - strObj.selfReplace("覈仁", "核仁") - strObj.selfReplace("覈心", "核心") - strObj.selfReplace("覈四", "核四") - strObj.selfReplace("覈果", "核果") - strObj.selfReplace("覈型", "核型") - strObj.selfReplace("覈苷", "核苷") - strObj.selfReplace("覈能", "核能") - strObj.selfReplace("覈傘", "核傘") - strObj.selfReplace("覈發", "核發") - strObj.selfReplace("覈電", "核電") - strObj.selfReplace("覈塵", "核塵") - strObj.selfReplace("覈酸", "核酸") - strObj.selfReplace("覈膜", "核膜") - strObj.selfReplace("覈爆", "核爆") - strObj.selfReplace("痔覈", "痔核") - strObj.selfReplace("陰覈", "陰核") - strObj.selfReplace("殽覈", "殽核") - strObj.selfReplace("結覈", "結核") - strObj.selfReplace("菌覈", "菌核") - strObj.selfReplace("煤覈", "煤核") - strObj.selfReplace("着涎茶", "著涎茶") - strObj.selfReplace("喫口令", "吃口令") - strObj.selfReplace("鄧艾喫", "鄧艾吃") - strObj.selfReplace("杏仁覈", "杏仁核") - strObj.selfReplace("覈一廠", "核一廠") - strObj.selfReplace("覈二廠", "核二廠") - strObj.selfReplace("覈三廠", "核三廠") - strObj.selfReplace("覈融合", "核融合") - strObj.selfReplace("覈四廠", "核四廠") - strObj.selfReplace("覈生化", "核生化") - strObj.selfReplace("覈災變", "核災變") - strObj.selfReplace("覈動力", "核動力") - strObj.selfReplace("覈試爆", "核試爆") - strObj.selfReplace("杏覈兒", "杏核兒") - strObj.selfReplace("原子覈", "原子核") - strObj.selfReplace("覈分裂", "核分裂") - strObj.selfReplace("覈化學", "核化學") - strObj.selfReplace("覈反應", "核反應") - strObj.selfReplace("覈半徑", "核半徑") - strObj.selfReplace("覈污染", "核污染") - strObj.selfReplace("覈武器", "核武器") - strObj.selfReplace("覈苷酸", "核苷酸") - strObj.selfReplace("覈蛋白", "核蛋白") - strObj.selfReplace("覈黃疸", "核黃疸") - strObj.selfReplace("覈黃素", "核黃素") - strObj.selfReplace("覈裝置", "核裝置") - strObj.selfReplace("覈電廠", "核電廠") - strObj.selfReplace("覈廢料", "核廢料") - strObj.selfReplace("覈彈頭", "核彈頭") - strObj.selfReplace("覈潛艇", "核潛艇") - strObj.selfReplace("覈燃料", "核燃料") - strObj.selfReplace("桃覈雕", "桃核雕") - strObj.selfReplace("細胞覈", "細胞核") - strObj.selfReplace("棗覈臉", "棗核臉") - strObj.selfReplace("以微知着", "以微知著") - strObj.selfReplace("見微知着", "見微知著") - strObj.selfReplace("恩威並着", "恩威並著") - strObj.selfReplace("視微知着", "視微知著") - strObj.selfReplace("睹微知着", "睹微知著") - strObj.selfReplace("遐邇着聞", "遐邇著聞") - strObj.selfReplace("積微成着", "積微成著") - strObj.selfReplace("地下覈試", "地下核試") - strObj.selfReplace("地下覈爆", "地下核爆") - strObj.selfReplace("非覈武區", "非核武區") - strObj.selfReplace("覈反應器", "核反應器") - strObj.selfReplace("覈物理學", "核物理學") - strObj.selfReplace("覈能發電", "核能發電") - strObj.selfReplace("覈能電廠", "核能電廠") - strObj.selfReplace("覈能廢料", "核能廢料") - strObj.selfReplace("覈能潛艇", "核能潛艇") - strObj.selfReplace("覈磁共振", "核磁共振") - strObj.selfReplace("熱覈反應", "熱核反應") - strObj.selfReplace("賣李鑽覈", "賣李鑽核") - strObj.selfReplace("雙覈都市", "雙核都市") - strObj.selfReplace("罵人不吐覈", "罵人不吐核") - return strObj - } + @objc class func cnvTradToKangXi(_ strObj: String) -> String { + var strObj = strObj + strObj.selfReplace("偽", "僞") + strObj.selfReplace("啟", "啓") + strObj.selfReplace("吃", "喫") + strObj.selfReplace("嫻", "嫺") + strObj.selfReplace("媯", "嬀") + strObj.selfReplace("峰", "峯") + strObj.selfReplace("么", "幺") + strObj.selfReplace("抬", "擡") + strObj.selfReplace("稜", "棱") + strObj.selfReplace("簷", "檐") + strObj.selfReplace("汙", "污") + strObj.selfReplace("洩", "泄") + strObj.selfReplace("溈", "潙") + strObj.selfReplace("潀", "潨") + strObj.selfReplace("為", "爲") + strObj.selfReplace("床", "牀") + strObj.selfReplace("痺", "痹") + strObj.selfReplace("痴", "癡") + strObj.selfReplace("皂", "皁") + strObj.selfReplace("著", "着") + strObj.selfReplace("睪", "睾") + strObj.selfReplace("秘", "祕") + strObj.selfReplace("灶", "竈") + strObj.selfReplace("粽", "糉") + strObj.selfReplace("韁", "繮") + strObj.selfReplace("才", "纔") + strObj.selfReplace("群", "羣") + strObj.selfReplace("唇", "脣") + strObj.selfReplace("參", "蔘") + strObj.selfReplace("蒍", "蔿") + strObj.selfReplace("眾", "衆") + strObj.selfReplace("裡", "裏") + strObj.selfReplace("核", "覈") + strObj.selfReplace("踴", "踊") + strObj.selfReplace("缽", "鉢") + strObj.selfReplace("針", "鍼") + strObj.selfReplace("鯰", "鮎") + strObj.selfReplace("麵", "麪") + strObj.selfReplace("顎", "齶") + strObj.selfReplace("口喫", "口吃") + strObj.selfReplace("合着", "合著") + strObj.selfReplace("名着", "名著") + strObj.selfReplace("巨着", "巨著") + strObj.selfReplace("鉅着", "鉅著") + strObj.selfReplace("昭着", "昭著") + strObj.selfReplace("所着", "所著") + strObj.selfReplace("遺着", "遺著") + strObj.selfReplace("顯着", "顯著") + strObj.selfReplace("土着", "土著") + strObj.selfReplace("着作", "著作") + strObj.selfReplace("着名", "著名") + strObj.selfReplace("着式", "著式") + strObj.selfReplace("着志", "著志") + strObj.selfReplace("着於", "著於") + strObj.selfReplace("着書", "著書") + strObj.selfReplace("着白", "著白") + strObj.selfReplace("着稱", "著稱") + strObj.selfReplace("着者", "著者") + strObj.selfReplace("着述", "著述") + strObj.selfReplace("着錄", "著錄") + strObj.selfReplace("蹇喫", "蹇吃") + strObj.selfReplace("大着", "大著") + strObj.selfReplace("刊着", "刊著") + strObj.selfReplace("玄着", "玄著") + strObj.selfReplace("白着", "白著") + strObj.selfReplace("住着", "住著") + strObj.selfReplace("刻着", "刻著") + strObj.selfReplace("卓着", "卓著") + strObj.selfReplace("拙着", "拙著") + strObj.selfReplace("查着", "查著") + strObj.selfReplace("炳着", "炳著") + strObj.selfReplace("原着", "原著") + strObj.selfReplace("專着", "專著") + strObj.selfReplace("焯着", "焯著") + strObj.selfReplace("着論", "著論") + strObj.selfReplace("着績", "著績") + strObj.selfReplace("較着", "較著") + strObj.selfReplace("彰着", "彰著") + strObj.selfReplace("撰着", "撰著") + strObj.selfReplace("編着", "編著") + strObj.selfReplace("論着", "論著") + strObj.selfReplace("雜着", "雜著") + strObj.selfReplace("譯着", "譯著") + strObj.selfReplace("地覈", "地核") + strObj.selfReplace("多覈", "多核") + strObj.selfReplace("氘覈", "氘核") + strObj.selfReplace("杏覈", "杏核") + strObj.selfReplace("非覈", "非核") + strObj.selfReplace("覈三", "核三") + strObj.selfReplace("覈下", "核下") + strObj.selfReplace("覈災", "核災") + strObj.selfReplace("覈武", "核武") + strObj.selfReplace("覈狀", "核狀") + strObj.selfReplace("覈桃", "核桃") + strObj.selfReplace("覈彈", "核彈") + strObj.selfReplace("覈戰", "核戰") + strObj.selfReplace("覈糖", "核糖") + strObj.selfReplace("覈醣", "核醣") + strObj.selfReplace("晶覈", "晶核") + strObj.selfReplace("熱覈", "熱核") + strObj.selfReplace("反覈", "反核") + strObj.selfReplace("卵覈", "卵核") + strObj.selfReplace("果覈", "果核") + strObj.selfReplace("剋覈", "剋核") + strObj.selfReplace("覈力", "核力") + strObj.selfReplace("覈子", "核子") + strObj.selfReplace("覈仁", "核仁") + strObj.selfReplace("覈心", "核心") + strObj.selfReplace("覈四", "核四") + strObj.selfReplace("覈果", "核果") + strObj.selfReplace("覈型", "核型") + strObj.selfReplace("覈苷", "核苷") + strObj.selfReplace("覈能", "核能") + strObj.selfReplace("覈傘", "核傘") + strObj.selfReplace("覈發", "核發") + strObj.selfReplace("覈電", "核電") + strObj.selfReplace("覈塵", "核塵") + strObj.selfReplace("覈酸", "核酸") + strObj.selfReplace("覈膜", "核膜") + strObj.selfReplace("覈爆", "核爆") + strObj.selfReplace("痔覈", "痔核") + strObj.selfReplace("陰覈", "陰核") + strObj.selfReplace("殽覈", "殽核") + strObj.selfReplace("結覈", "結核") + strObj.selfReplace("菌覈", "菌核") + strObj.selfReplace("煤覈", "煤核") + strObj.selfReplace("着涎茶", "著涎茶") + strObj.selfReplace("喫口令", "吃口令") + strObj.selfReplace("鄧艾喫", "鄧艾吃") + strObj.selfReplace("杏仁覈", "杏仁核") + strObj.selfReplace("覈一廠", "核一廠") + strObj.selfReplace("覈二廠", "核二廠") + strObj.selfReplace("覈三廠", "核三廠") + strObj.selfReplace("覈融合", "核融合") + strObj.selfReplace("覈四廠", "核四廠") + strObj.selfReplace("覈生化", "核生化") + strObj.selfReplace("覈災變", "核災變") + strObj.selfReplace("覈動力", "核動力") + strObj.selfReplace("覈試爆", "核試爆") + strObj.selfReplace("杏覈兒", "杏核兒") + strObj.selfReplace("原子覈", "原子核") + strObj.selfReplace("覈分裂", "核分裂") + strObj.selfReplace("覈化學", "核化學") + strObj.selfReplace("覈反應", "核反應") + strObj.selfReplace("覈半徑", "核半徑") + strObj.selfReplace("覈污染", "核污染") + strObj.selfReplace("覈武器", "核武器") + strObj.selfReplace("覈苷酸", "核苷酸") + strObj.selfReplace("覈蛋白", "核蛋白") + strObj.selfReplace("覈黃疸", "核黃疸") + strObj.selfReplace("覈黃素", "核黃素") + strObj.selfReplace("覈裝置", "核裝置") + strObj.selfReplace("覈電廠", "核電廠") + strObj.selfReplace("覈廢料", "核廢料") + strObj.selfReplace("覈彈頭", "核彈頭") + strObj.selfReplace("覈潛艇", "核潛艇") + strObj.selfReplace("覈燃料", "核燃料") + strObj.selfReplace("桃覈雕", "桃核雕") + strObj.selfReplace("細胞覈", "細胞核") + strObj.selfReplace("棗覈臉", "棗核臉") + strObj.selfReplace("以微知着", "以微知著") + strObj.selfReplace("見微知着", "見微知著") + strObj.selfReplace("恩威並着", "恩威並著") + strObj.selfReplace("視微知着", "視微知著") + strObj.selfReplace("睹微知着", "睹微知著") + strObj.selfReplace("遐邇着聞", "遐邇著聞") + strObj.selfReplace("積微成着", "積微成著") + strObj.selfReplace("地下覈試", "地下核試") + strObj.selfReplace("地下覈爆", "地下核爆") + strObj.selfReplace("非覈武區", "非核武區") + strObj.selfReplace("覈反應器", "核反應器") + strObj.selfReplace("覈物理學", "核物理學") + strObj.selfReplace("覈能發電", "核能發電") + strObj.selfReplace("覈能電廠", "核能電廠") + strObj.selfReplace("覈能廢料", "核能廢料") + strObj.selfReplace("覈能潛艇", "核能潛艇") + strObj.selfReplace("覈磁共振", "核磁共振") + strObj.selfReplace("熱覈反應", "熱核反應") + strObj.selfReplace("賣李鑽覈", "賣李鑽核") + strObj.selfReplace("雙覈都市", "雙核都市") + strObj.selfReplace("罵人不吐覈", "罵人不吐核") + return strObj + } - @objc class func cnvTradToJIS(_ strObj: String) -> String { - // 該轉換是由康熙繁體轉換至日語當用漢字的,所以需要先跑一遍康熙轉換。 - var strObj = cnvTradToKangXi(strObj) - strObj.selfReplace("兩", "両") - strObj.selfReplace("輛", "両") - strObj.selfReplace("辨", "弁") - strObj.selfReplace("辯", "弁") - strObj.selfReplace("瓣", "弁") - strObj.selfReplace("辦", "弁") - strObj.selfReplace("禦", "御") - strObj.selfReplace("缺", "欠") - strObj.selfReplace("絲", "糸") - strObj.selfReplace("藝", "芸") - strObj.selfReplace("濱", "浜") - strObj.selfReplace("乘", "乗") - strObj.selfReplace("亂", "乱") - strObj.selfReplace("亙", "亘") - strObj.selfReplace("亞", "亜") - strObj.selfReplace("佛", "仏") - strObj.selfReplace("來", "来") - strObj.selfReplace("假", "仮") - strObj.selfReplace("傳", "伝") - strObj.selfReplace("僞", "偽") - strObj.selfReplace("價", "価") - strObj.selfReplace("儉", "倹") - strObj.selfReplace("兒", "児") - strObj.selfReplace("內", "内") - strObj.selfReplace("剎", "刹") - strObj.selfReplace("剩", "剰") - strObj.selfReplace("劍", "剣") - strObj.selfReplace("剱", "剣") - strObj.selfReplace("劎", "剣") - strObj.selfReplace("劒", "剣") - strObj.selfReplace("劔", "剣") - strObj.selfReplace("劑", "剤") - strObj.selfReplace("勞", "労") - strObj.selfReplace("勳", "勲") - strObj.selfReplace("勵", "励") - strObj.selfReplace("勸", "勧") - strObj.selfReplace("勻", "匀") - strObj.selfReplace("區", "区") - strObj.selfReplace("卷", "巻") - strObj.selfReplace("卻", "却") - strObj.selfReplace("參", "参") - strObj.selfReplace("吳", "呉") - strObj.selfReplace("咒", "呪") - strObj.selfReplace("啞", "唖") - strObj.selfReplace("單", "単") - strObj.selfReplace("噓", "嘘") - strObj.selfReplace("嚙", "噛") - strObj.selfReplace("嚴", "厳") - strObj.selfReplace("囑", "嘱") - strObj.selfReplace("圈", "圏") - strObj.selfReplace("國", "国") - strObj.selfReplace("圍", "囲") - strObj.selfReplace("圓", "円") - strObj.selfReplace("圖", "図") - strObj.selfReplace("團", "団") - strObj.selfReplace("增", "増") - strObj.selfReplace("墮", "堕") - strObj.selfReplace("壓", "圧") - strObj.selfReplace("壘", "塁") - strObj.selfReplace("壞", "壊") - strObj.selfReplace("壤", "壌") - strObj.selfReplace("壯", "壮") - strObj.selfReplace("壹", "壱") - strObj.selfReplace("壽", "寿") - strObj.selfReplace("奧", "奥") - strObj.selfReplace("奬", "奨") - strObj.selfReplace("妝", "粧") - strObj.selfReplace("孃", "嬢") - strObj.selfReplace("學", "学") - strObj.selfReplace("寢", "寝") - strObj.selfReplace("實", "実") - strObj.selfReplace("寫", "写") - strObj.selfReplace("寬", "寛") - strObj.selfReplace("寶", "宝") - strObj.selfReplace("將", "将") - strObj.selfReplace("專", "専") - strObj.selfReplace("對", "対") - strObj.selfReplace("屆", "届") - strObj.selfReplace("屬", "属") - strObj.selfReplace("峯", "峰") - strObj.selfReplace("峽", "峡") - strObj.selfReplace("嶽", "岳") - strObj.selfReplace("巖", "巌") - strObj.selfReplace("巢", "巣") - strObj.selfReplace("帶", "帯") - strObj.selfReplace("廁", "厠") - strObj.selfReplace("廢", "廃") - strObj.selfReplace("廣", "広") - strObj.selfReplace("廳", "庁") - strObj.selfReplace("彈", "弾") - strObj.selfReplace("彌", "弥") - strObj.selfReplace("彎", "弯") - strObj.selfReplace("彥", "彦") - strObj.selfReplace("徑", "径") - strObj.selfReplace("從", "従") - strObj.selfReplace("徵", "徴") - strObj.selfReplace("德", "徳") - strObj.selfReplace("恆", "恒") - strObj.selfReplace("悅", "悦") - strObj.selfReplace("惠", "恵") - strObj.selfReplace("惡", "悪") - strObj.selfReplace("惱", "悩") - strObj.selfReplace("慘", "惨") - strObj.selfReplace("應", "応") - strObj.selfReplace("懷", "懐") - strObj.selfReplace("戀", "恋") - strObj.selfReplace("戰", "戦") - strObj.selfReplace("戲", "戯") - strObj.selfReplace("戶", "戸") - strObj.selfReplace("戾", "戻") - strObj.selfReplace("拂", "払") - strObj.selfReplace("拔", "抜") - strObj.selfReplace("拜", "拝") - strObj.selfReplace("挾", "挟") - strObj.selfReplace("插", "挿") - strObj.selfReplace("揭", "掲") - strObj.selfReplace("搔", "掻") - strObj.selfReplace("搖", "揺") - strObj.selfReplace("搜", "捜") - strObj.selfReplace("摑", "掴") - strObj.selfReplace("擇", "択") - strObj.selfReplace("擊", "撃") - strObj.selfReplace("擔", "担") - strObj.selfReplace("據", "拠") - strObj.selfReplace("擴", "拡") - strObj.selfReplace("攝", "摂") - strObj.selfReplace("攪", "撹") - strObj.selfReplace("收", "収") - strObj.selfReplace("效", "効") - strObj.selfReplace("敕", "勅") - strObj.selfReplace("敘", "叙") - strObj.selfReplace("數", "数") - strObj.selfReplace("斷", "断") - strObj.selfReplace("晉", "晋") - strObj.selfReplace("晚", "晩") - strObj.selfReplace("晝", "昼") - strObj.selfReplace("暨", "曁") - strObj.selfReplace("曆", "暦") - strObj.selfReplace("曉", "暁") - strObj.selfReplace("曾", "曽") - strObj.selfReplace("會", "会") - strObj.selfReplace("枡", "桝") - strObj.selfReplace("查", "査") - strObj.selfReplace("條", "条") - strObj.selfReplace("棧", "桟") - strObj.selfReplace("棱", "稜") - strObj.selfReplace("榆", "楡") - strObj.selfReplace("榮", "栄") - strObj.selfReplace("樂", "楽") - strObj.selfReplace("樓", "楼") - strObj.selfReplace("樞", "枢") - strObj.selfReplace("樣", "様") - strObj.selfReplace("橫", "横") - strObj.selfReplace("檢", "検") - strObj.selfReplace("櫻", "桜") - strObj.selfReplace("權", "権") - strObj.selfReplace("歐", "欧") - strObj.selfReplace("歡", "歓") - strObj.selfReplace("步", "歩") - strObj.selfReplace("歲", "歳") - strObj.selfReplace("歷", "歴") - strObj.selfReplace("歸", "帰") - strObj.selfReplace("殘", "残") - strObj.selfReplace("殼", "殻") - strObj.selfReplace("毆", "殴") - strObj.selfReplace("每", "毎") - strObj.selfReplace("氣", "気") - strObj.selfReplace("污", "汚") - strObj.selfReplace("沒", "没") - strObj.selfReplace("涉", "渉") - strObj.selfReplace("淚", "涙") - strObj.selfReplace("淨", "浄") - strObj.selfReplace("淺", "浅") - strObj.selfReplace("渴", "渇") - strObj.selfReplace("溌", "潑") - strObj.selfReplace("溪", "渓") - strObj.selfReplace("溫", "温") - strObj.selfReplace("溼", "湿") - strObj.selfReplace("滯", "滞") - strObj.selfReplace("滿", "満") - strObj.selfReplace("潛", "潜") - strObj.selfReplace("澀", "渋") - strObj.selfReplace("澤", "沢") - strObj.selfReplace("濟", "済") - strObj.selfReplace("濤", "涛") - strObj.selfReplace("濾", "沪") - strObj.selfReplace("瀧", "滝") - strObj.selfReplace("瀨", "瀬") - strObj.selfReplace("灣", "湾") - strObj.selfReplace("焰", "焔") - strObj.selfReplace("燈", "灯") - strObj.selfReplace("燒", "焼") - strObj.selfReplace("營", "営") - strObj.selfReplace("爐", "炉") - strObj.selfReplace("爭", "争") - strObj.selfReplace("爲", "為") - strObj.selfReplace("牀", "床") - strObj.selfReplace("犧", "犠") - strObj.selfReplace("狀", "状") - strObj.selfReplace("狹", "狭") - strObj.selfReplace("獨", "独") - strObj.selfReplace("獵", "猟") - strObj.selfReplace("獸", "獣") - strObj.selfReplace("獻", "献") - strObj.selfReplace("產", "産") - strObj.selfReplace("畫", "画") - strObj.selfReplace("當", "当") - strObj.selfReplace("疊", "畳") - strObj.selfReplace("疎", "疏") - strObj.selfReplace("痹", "痺") - strObj.selfReplace("瘦", "痩") - strObj.selfReplace("癡", "痴") - strObj.selfReplace("發", "発") - strObj.selfReplace("皋", "皐") - strObj.selfReplace("盜", "盗") - strObj.selfReplace("盡", "尽") - strObj.selfReplace("碎", "砕") - strObj.selfReplace("祕", "秘") - strObj.selfReplace("祿", "禄") - strObj.selfReplace("禪", "禅") - strObj.selfReplace("禮", "礼") - strObj.selfReplace("禱", "祷") - strObj.selfReplace("稅", "税") - strObj.selfReplace("稱", "称") - strObj.selfReplace("稻", "稲") - strObj.selfReplace("穎", "頴") - strObj.selfReplace("穗", "穂") - strObj.selfReplace("穩", "穏") - strObj.selfReplace("穰", "穣") - strObj.selfReplace("竃", "竈") - strObj.selfReplace("竊", "窃") - strObj.selfReplace("粹", "粋") - strObj.selfReplace("糉", "粽") - strObj.selfReplace("絕", "絶") - strObj.selfReplace("經", "経") - strObj.selfReplace("綠", "緑") - strObj.selfReplace("緖", "緒") - strObj.selfReplace("緣", "縁") - strObj.selfReplace("縣", "県") - strObj.selfReplace("縱", "縦") - strObj.selfReplace("總", "総") - strObj.selfReplace("繋", "繫") - strObj.selfReplace("繡", "繍") - strObj.selfReplace("繩", "縄") - strObj.selfReplace("繪", "絵") - strObj.selfReplace("繼", "継") - strObj.selfReplace("續", "続") - strObj.selfReplace("纔", "才") - strObj.selfReplace("纖", "繊") - strObj.selfReplace("罐", "缶") - strObj.selfReplace("羣", "群") - strObj.selfReplace("聯", "連") - strObj.selfReplace("聰", "聡") - strObj.selfReplace("聲", "声") - strObj.selfReplace("聽", "聴") - strObj.selfReplace("肅", "粛") - strObj.selfReplace("脣", "唇") - strObj.selfReplace("脫", "脱") - strObj.selfReplace("腦", "脳") - strObj.selfReplace("腳", "脚") - strObj.selfReplace("膽", "胆") - strObj.selfReplace("臟", "臓") - strObj.selfReplace("臺", "台") - strObj.selfReplace("與", "与") - strObj.selfReplace("舉", "挙") - strObj.selfReplace("舊", "旧") - strObj.selfReplace("舍", "舎") - strObj.selfReplace("荔", "茘") - strObj.selfReplace("莊", "荘") - strObj.selfReplace("莖", "茎") - strObj.selfReplace("菸", "煙") - strObj.selfReplace("萊", "莱") - strObj.selfReplace("萬", "万") - strObj.selfReplace("蔣", "蒋") - strObj.selfReplace("蔥", "葱") - strObj.selfReplace("薰", "薫") - strObj.selfReplace("藏", "蔵") - strObj.selfReplace("藥", "薬") - strObj.selfReplace("蘆", "芦") - strObj.selfReplace("處", "処") - strObj.selfReplace("虛", "虚") - strObj.selfReplace("號", "号") - strObj.selfReplace("螢", "蛍") - strObj.selfReplace("蟲", "虫") - strObj.selfReplace("蠟", "蝋") - strObj.selfReplace("蠶", "蚕") - strObj.selfReplace("蠻", "蛮") - strObj.selfReplace("裝", "装") - strObj.selfReplace("覺", "覚") - strObj.selfReplace("覽", "覧") - strObj.selfReplace("觀", "観") - strObj.selfReplace("觸", "触") - strObj.selfReplace("說", "説") - strObj.selfReplace("謠", "謡") - strObj.selfReplace("證", "証") - strObj.selfReplace("譯", "訳") - strObj.selfReplace("譽", "誉") - strObj.selfReplace("讀", "読") - strObj.selfReplace("變", "変") - strObj.selfReplace("讓", "譲") - strObj.selfReplace("豐", "豊") - strObj.selfReplace("豫", "予") - strObj.selfReplace("貓", "猫") - strObj.selfReplace("貳", "弐") - strObj.selfReplace("賣", "売") - strObj.selfReplace("賴", "頼") - strObj.selfReplace("贊", "賛") - strObj.selfReplace("贗", "贋") - strObj.selfReplace("踐", "践") - strObj.selfReplace("輕", "軽") - strObj.selfReplace("輛", "輌") - strObj.selfReplace("轉", "転") - strObj.selfReplace("辭", "辞") - strObj.selfReplace("遞", "逓") - strObj.selfReplace("遥", "遙") - strObj.selfReplace("遲", "遅") - strObj.selfReplace("邊", "辺") - strObj.selfReplace("鄉", "郷") - strObj.selfReplace("酢", "醋") - strObj.selfReplace("醉", "酔") - strObj.selfReplace("醗", "醱") - strObj.selfReplace("醫", "医") - strObj.selfReplace("醬", "醤") - strObj.selfReplace("釀", "醸") - strObj.selfReplace("釋", "釈") - strObj.selfReplace("鋪", "舗") - strObj.selfReplace("錄", "録") - strObj.selfReplace("錢", "銭") - strObj.selfReplace("鍊", "錬") - strObj.selfReplace("鐵", "鉄") - strObj.selfReplace("鑄", "鋳") - strObj.selfReplace("鑛", "鉱") - strObj.selfReplace("閱", "閲") - strObj.selfReplace("關", "関") - strObj.selfReplace("陷", "陥") - strObj.selfReplace("隨", "随") - strObj.selfReplace("險", "険") - strObj.selfReplace("隱", "隠") - strObj.selfReplace("雙", "双") - strObj.selfReplace("雜", "雑") - strObj.selfReplace("雞", "鶏") - strObj.selfReplace("霸", "覇") - strObj.selfReplace("靈", "霊") - strObj.selfReplace("靜", "静") - strObj.selfReplace("顏", "顔") - strObj.selfReplace("顯", "顕") - strObj.selfReplace("餘", "余") - strObj.selfReplace("騷", "騒") - strObj.selfReplace("驅", "駆") - strObj.selfReplace("驗", "験") - strObj.selfReplace("驛", "駅") - strObj.selfReplace("髓", "髄") - strObj.selfReplace("體", "体") - strObj.selfReplace("髮", "髪") - strObj.selfReplace("鬥", "闘") - strObj.selfReplace("鱉", "鼈") - strObj.selfReplace("鷗", "鴎") - strObj.selfReplace("鹼", "鹸") - strObj.selfReplace("鹽", "塩") - strObj.selfReplace("麥", "麦") - strObj.selfReplace("麪", "麺") - strObj.selfReplace("麴", "麹") - strObj.selfReplace("黃", "黄") - strObj.selfReplace("黑", "黒") - strObj.selfReplace("默", "黙") - strObj.selfReplace("點", "点") - strObj.selfReplace("黨", "党") - strObj.selfReplace("齊", "斉") - strObj.selfReplace("齋", "斎") - strObj.selfReplace("齒", "歯") - strObj.selfReplace("齡", "齢") - strObj.selfReplace("龍", "竜") - strObj.selfReplace("龜", "亀") - strObj.selfReplace("叮嚀", "丁寧") - strObj.selfReplace("鄭重", "丁重") - strObj.selfReplace("輿論", "世論") - strObj.selfReplace("唖鈴", "亜鈴") - strObj.selfReplace("交叉", "交差") - strObj.selfReplace("饗宴", "供宴") - strObj.selfReplace("駿馬", "俊馬") - strObj.selfReplace("堡塁", "保塁") - strObj.selfReplace("扁平", "偏平") - strObj.selfReplace("碇泊", "停泊") - strObj.selfReplace("優駿", "優俊") - strObj.selfReplace("尖兵", "先兵") - strObj.selfReplace("尖鋭", "先鋭") - strObj.selfReplace("共軛", "共役") - strObj.selfReplace("饒舌", "冗舌") - strObj.selfReplace("兇器", "凶器") - strObj.selfReplace("鑿岩", "削岩") - strObj.selfReplace("庖丁", "包丁") - strObj.selfReplace("繃帯", "包帯") - strObj.selfReplace("区劃", "区画") - strObj.selfReplace("儼然", "厳然") - strObj.selfReplace("友誼", "友宜") - strObj.selfReplace("叛乱", "反乱") - strObj.selfReplace("蒐集", "収集") - strObj.selfReplace("抒情", "叙情") - strObj.selfReplace("擡頭", "台頭") - strObj.selfReplace("合弁", "合弁") - strObj.selfReplace("歎願", "嘆願") - strObj.selfReplace("廻転", "回転") - strObj.selfReplace("回游", "回遊") - strObj.selfReplace("捧持", "奉持") - strObj.selfReplace("萎縮", "委縮") - strObj.selfReplace("輾転", "展転") - strObj.selfReplace("稀少", "希少") - strObj.selfReplace("眩惑", "幻惑") - strObj.selfReplace("広汎", "広範") - strObj.selfReplace("曠野", "広野") - strObj.selfReplace("廃墟", "廃虚") - strObj.selfReplace("弁当", "弁当") - strObj.selfReplace("弁膜", "弁膜") - strObj.selfReplace("弁護", "弁護") - strObj.selfReplace("辮髪", "弁髪") - strObj.selfReplace("絃歌", "弦歌") - strObj.selfReplace("恩誼", "恩義") - strObj.selfReplace("意嚮", "意向") - strObj.selfReplace("臆断", "憶断") - strObj.selfReplace("臆病", "憶病") - strObj.selfReplace("戦歿", "戦没") - strObj.selfReplace("煽情", "扇情") - strObj.selfReplace("手帖", "手帳") - strObj.selfReplace("伎倆", "技量") - strObj.selfReplace("抜萃", "抜粋") - strObj.selfReplace("披瀝", "披歴") - strObj.selfReplace("牴触", "抵触") - strObj.selfReplace("抽籤", "抽選") - strObj.selfReplace("勾引", "拘引") - strObj.selfReplace("醵出", "拠出") - strObj.selfReplace("醵金", "拠金") - strObj.selfReplace("掘鑿", "掘削") - strObj.selfReplace("扣除", "控除") - strObj.selfReplace("掩護", "援護") - strObj.selfReplace("抛棄", "放棄") - strObj.selfReplace("撒水", "散水") - strObj.selfReplace("敬虔", "敬謙") - strObj.selfReplace("敷衍", "敷延") - strObj.selfReplace("断乎", "断固") - strObj.selfReplace("簇生", "族生") - strObj.selfReplace("陞叙", "昇叙") - strObj.selfReplace("煖房", "暖房") - strObj.selfReplace("暗誦", "暗唱") - strObj.selfReplace("闇夜", "暗夜") - strObj.selfReplace("曝露", "暴露") - strObj.selfReplace("涸渇", "枯渇") - strObj.selfReplace("恰好", "格好") - strObj.selfReplace("恰幅", "格幅") - strObj.selfReplace("毀損", "棄損") - strObj.selfReplace("摸索", "模索") - strObj.selfReplace("欠欠", "欠缺") - strObj.selfReplace("屍体", "死体") - strObj.selfReplace("臀部", "殿部") - strObj.selfReplace("拇指", "母指") - strObj.selfReplace("気魄", "気迫") - strObj.selfReplace("訣別", "決別") - strObj.selfReplace("決潰", "決壊") - strObj.selfReplace("沈澱", "沈殿") - strObj.selfReplace("波瀾", "波乱") - strObj.selfReplace("註釈", "注釈") - strObj.selfReplace("洗滌", "洗濯") - strObj.selfReplace("活潑", "活発") - strObj.selfReplace("滲透", "浸透") - strObj.selfReplace("浸蝕", "浸食") - strObj.selfReplace("銷却", "消却") - strObj.selfReplace("渾然", "混然") - strObj.selfReplace("弯曲", "湾曲") - strObj.selfReplace("熔接", "溶接") - strObj.selfReplace("漁撈", "漁労") - strObj.selfReplace("飄然", "漂然") - strObj.selfReplace("激昂", "激高") - strObj.selfReplace("火焔", "火炎") - strObj.selfReplace("焦躁", "焦燥") - strObj.selfReplace("斑点", "班点") - strObj.selfReplace("溜飲", "留飲") - strObj.selfReplace("掠奪", "略奪") - strObj.selfReplace("疏通", "疎通") - strObj.selfReplace("醱酵", "発酵") - strObj.selfReplace("白堊", "白亜") - strObj.selfReplace("相剋", "相克") - strObj.selfReplace("智慧", "知恵") - strObj.selfReplace("破毀", "破棄") - strObj.selfReplace("確乎", "確固") - strObj.selfReplace("禁錮", "禁固") - strObj.selfReplace("符牒", "符丁") - strObj.selfReplace("扮装", "粉装") - strObj.selfReplace("紫斑", "紫班") - strObj.selfReplace("終熄", "終息") - strObj.selfReplace("綜合", "総合") - strObj.selfReplace("編輯", "編集") - strObj.selfReplace("義捐", "義援") - strObj.selfReplace("肝腎", "肝心") - strObj.selfReplace("悖徳", "背徳") - strObj.selfReplace("脈搏", "脈拍") - strObj.selfReplace("膨脹", "膨張") - strObj.selfReplace("芳醇", "芳純") - strObj.selfReplace("叡智", "英知") - strObj.selfReplace("蒸溜", "蒸留") - strObj.selfReplace("燻蒸", "薫蒸") - strObj.selfReplace("燻製", "薫製") - strObj.selfReplace("衣裳", "衣装") - strObj.selfReplace("衰頽", "衰退") - strObj.selfReplace("悠然", "裕然") - strObj.selfReplace("輔佐", "補佐") - strObj.selfReplace("訓誡", "訓戒") - strObj.selfReplace("試煉", "試練") - strObj.selfReplace("詭弁", "詭弁") - strObj.selfReplace("媾和", "講和") - strObj.selfReplace("象嵌", "象眼") - strObj.selfReplace("貫禄", "貫録") - strObj.selfReplace("買弁", "買弁") - strObj.selfReplace("讚辞", "賛辞") - strObj.selfReplace("蹈襲", "踏襲") - strObj.selfReplace("車両", "車両") - strObj.selfReplace("顛倒", "転倒") - strObj.selfReplace("輪廓", "輪郭") - strObj.selfReplace("褪色", "退色") - strObj.selfReplace("杜絶", "途絶") - strObj.selfReplace("連繫", "連係") - strObj.selfReplace("連合", "連合") - strObj.selfReplace("銓衡", "選考") - strObj.selfReplace("醋酸", "酢酸") - strObj.selfReplace("野鄙", "野卑") - strObj.selfReplace("礦石", "鉱石") - strObj.selfReplace("間歇", "間欠") - strObj.selfReplace("函数", "関数") - strObj.selfReplace("防御", "防御") - strObj.selfReplace("嶮岨", "険阻") - strObj.selfReplace("牆壁", "障壁") - strObj.selfReplace("障礙", "障害") - strObj.selfReplace("湮滅", "隠滅") - strObj.selfReplace("聚落", "集落") - strObj.selfReplace("雇傭", "雇用") - strObj.selfReplace("諷喩", "風諭") - strObj.selfReplace("蜚語", "飛語") - strObj.selfReplace("香奠", "香典") - strObj.selfReplace("骨骼", "骨格") - strObj.selfReplace("亢進", "高進") - strObj.selfReplace("鳥瞰", "鳥観") - strObj.selfReplace("一攫", "一獲") - strObj.selfReplace("肩胛", "肩甲") - strObj.selfReplace("箇条", "個条") - strObj.selfReplace("啓動", "起動") - strObj.selfReplace("三叉路", "三差路") - strObj.selfReplace("嬉遊曲", "喜遊曲") - strObj.selfReplace("建蔽率", "建坪率") - strObj.selfReplace("慰藉料", "慰謝料") - strObj.selfReplace("橋頭堡", "橋頭保") - strObj.selfReplace("油槽船", "油送船") - strObj.selfReplace("耕耘機", "耕運機") - return strObj - } + @objc class func cnvTradToJIS(_ strObj: String) -> String { + // 該轉換是由康熙繁體轉換至日語當用漢字的,所以需要先跑一遍康熙轉換。 + var strObj = cnvTradToKangXi(strObj) + strObj.selfReplace("兩", "両") + strObj.selfReplace("輛", "両") + strObj.selfReplace("辨", "弁") + strObj.selfReplace("辯", "弁") + strObj.selfReplace("瓣", "弁") + strObj.selfReplace("辦", "弁") + strObj.selfReplace("禦", "御") + strObj.selfReplace("缺", "欠") + strObj.selfReplace("絲", "糸") + strObj.selfReplace("藝", "芸") + strObj.selfReplace("濱", "浜") + strObj.selfReplace("乘", "乗") + strObj.selfReplace("亂", "乱") + strObj.selfReplace("亙", "亘") + strObj.selfReplace("亞", "亜") + strObj.selfReplace("佛", "仏") + strObj.selfReplace("來", "来") + strObj.selfReplace("假", "仮") + strObj.selfReplace("傳", "伝") + strObj.selfReplace("僞", "偽") + strObj.selfReplace("價", "価") + strObj.selfReplace("儉", "倹") + strObj.selfReplace("兒", "児") + strObj.selfReplace("內", "内") + strObj.selfReplace("剎", "刹") + strObj.selfReplace("剩", "剰") + strObj.selfReplace("劍", "剣") + strObj.selfReplace("剱", "剣") + strObj.selfReplace("劎", "剣") + strObj.selfReplace("劒", "剣") + strObj.selfReplace("劔", "剣") + strObj.selfReplace("劑", "剤") + strObj.selfReplace("勞", "労") + strObj.selfReplace("勳", "勲") + strObj.selfReplace("勵", "励") + strObj.selfReplace("勸", "勧") + strObj.selfReplace("勻", "匀") + strObj.selfReplace("區", "区") + strObj.selfReplace("卷", "巻") + strObj.selfReplace("卻", "却") + strObj.selfReplace("參", "参") + strObj.selfReplace("吳", "呉") + strObj.selfReplace("咒", "呪") + strObj.selfReplace("啞", "唖") + strObj.selfReplace("單", "単") + strObj.selfReplace("噓", "嘘") + strObj.selfReplace("嚙", "噛") + strObj.selfReplace("嚴", "厳") + strObj.selfReplace("囑", "嘱") + strObj.selfReplace("圈", "圏") + strObj.selfReplace("國", "国") + strObj.selfReplace("圍", "囲") + strObj.selfReplace("圓", "円") + strObj.selfReplace("圖", "図") + strObj.selfReplace("團", "団") + strObj.selfReplace("增", "増") + strObj.selfReplace("墮", "堕") + strObj.selfReplace("壓", "圧") + strObj.selfReplace("壘", "塁") + strObj.selfReplace("壞", "壊") + strObj.selfReplace("壤", "壌") + strObj.selfReplace("壯", "壮") + strObj.selfReplace("壹", "壱") + strObj.selfReplace("壽", "寿") + strObj.selfReplace("奧", "奥") + strObj.selfReplace("奬", "奨") + strObj.selfReplace("妝", "粧") + strObj.selfReplace("孃", "嬢") + strObj.selfReplace("學", "学") + strObj.selfReplace("寢", "寝") + strObj.selfReplace("實", "実") + strObj.selfReplace("寫", "写") + strObj.selfReplace("寬", "寛") + strObj.selfReplace("寶", "宝") + strObj.selfReplace("將", "将") + strObj.selfReplace("專", "専") + strObj.selfReplace("對", "対") + strObj.selfReplace("屆", "届") + strObj.selfReplace("屬", "属") + strObj.selfReplace("峯", "峰") + strObj.selfReplace("峽", "峡") + strObj.selfReplace("嶽", "岳") + strObj.selfReplace("巖", "巌") + strObj.selfReplace("巢", "巣") + strObj.selfReplace("帶", "帯") + strObj.selfReplace("廁", "厠") + strObj.selfReplace("廢", "廃") + strObj.selfReplace("廣", "広") + strObj.selfReplace("廳", "庁") + strObj.selfReplace("彈", "弾") + strObj.selfReplace("彌", "弥") + strObj.selfReplace("彎", "弯") + strObj.selfReplace("彥", "彦") + strObj.selfReplace("徑", "径") + strObj.selfReplace("從", "従") + strObj.selfReplace("徵", "徴") + strObj.selfReplace("德", "徳") + strObj.selfReplace("恆", "恒") + strObj.selfReplace("悅", "悦") + strObj.selfReplace("惠", "恵") + strObj.selfReplace("惡", "悪") + strObj.selfReplace("惱", "悩") + strObj.selfReplace("慘", "惨") + strObj.selfReplace("應", "応") + strObj.selfReplace("懷", "懐") + strObj.selfReplace("戀", "恋") + strObj.selfReplace("戰", "戦") + strObj.selfReplace("戲", "戯") + strObj.selfReplace("戶", "戸") + strObj.selfReplace("戾", "戻") + strObj.selfReplace("拂", "払") + strObj.selfReplace("拔", "抜") + strObj.selfReplace("拜", "拝") + strObj.selfReplace("挾", "挟") + strObj.selfReplace("插", "挿") + strObj.selfReplace("揭", "掲") + strObj.selfReplace("搔", "掻") + strObj.selfReplace("搖", "揺") + strObj.selfReplace("搜", "捜") + strObj.selfReplace("摑", "掴") + strObj.selfReplace("擇", "択") + strObj.selfReplace("擊", "撃") + strObj.selfReplace("擔", "担") + strObj.selfReplace("據", "拠") + strObj.selfReplace("擴", "拡") + strObj.selfReplace("攝", "摂") + strObj.selfReplace("攪", "撹") + strObj.selfReplace("收", "収") + strObj.selfReplace("效", "効") + strObj.selfReplace("敕", "勅") + strObj.selfReplace("敘", "叙") + strObj.selfReplace("數", "数") + strObj.selfReplace("斷", "断") + strObj.selfReplace("晉", "晋") + strObj.selfReplace("晚", "晩") + strObj.selfReplace("晝", "昼") + strObj.selfReplace("暨", "曁") + strObj.selfReplace("曆", "暦") + strObj.selfReplace("曉", "暁") + strObj.selfReplace("曾", "曽") + strObj.selfReplace("會", "会") + strObj.selfReplace("枡", "桝") + strObj.selfReplace("查", "査") + strObj.selfReplace("條", "条") + strObj.selfReplace("棧", "桟") + strObj.selfReplace("棱", "稜") + strObj.selfReplace("榆", "楡") + strObj.selfReplace("榮", "栄") + strObj.selfReplace("樂", "楽") + strObj.selfReplace("樓", "楼") + strObj.selfReplace("樞", "枢") + strObj.selfReplace("樣", "様") + strObj.selfReplace("橫", "横") + strObj.selfReplace("檢", "検") + strObj.selfReplace("櫻", "桜") + strObj.selfReplace("權", "権") + strObj.selfReplace("歐", "欧") + strObj.selfReplace("歡", "歓") + strObj.selfReplace("步", "歩") + strObj.selfReplace("歲", "歳") + strObj.selfReplace("歷", "歴") + strObj.selfReplace("歸", "帰") + strObj.selfReplace("殘", "残") + strObj.selfReplace("殼", "殻") + strObj.selfReplace("毆", "殴") + strObj.selfReplace("每", "毎") + strObj.selfReplace("氣", "気") + strObj.selfReplace("污", "汚") + strObj.selfReplace("沒", "没") + strObj.selfReplace("涉", "渉") + strObj.selfReplace("淚", "涙") + strObj.selfReplace("淨", "浄") + strObj.selfReplace("淺", "浅") + strObj.selfReplace("渴", "渇") + strObj.selfReplace("溌", "潑") + strObj.selfReplace("溪", "渓") + strObj.selfReplace("溫", "温") + strObj.selfReplace("溼", "湿") + strObj.selfReplace("滯", "滞") + strObj.selfReplace("滿", "満") + strObj.selfReplace("潛", "潜") + strObj.selfReplace("澀", "渋") + strObj.selfReplace("澤", "沢") + strObj.selfReplace("濟", "済") + strObj.selfReplace("濤", "涛") + strObj.selfReplace("濾", "沪") + strObj.selfReplace("瀧", "滝") + strObj.selfReplace("瀨", "瀬") + strObj.selfReplace("灣", "湾") + strObj.selfReplace("焰", "焔") + strObj.selfReplace("燈", "灯") + strObj.selfReplace("燒", "焼") + strObj.selfReplace("營", "営") + strObj.selfReplace("爐", "炉") + strObj.selfReplace("爭", "争") + strObj.selfReplace("爲", "為") + strObj.selfReplace("牀", "床") + strObj.selfReplace("犧", "犠") + strObj.selfReplace("狀", "状") + strObj.selfReplace("狹", "狭") + strObj.selfReplace("獨", "独") + strObj.selfReplace("獵", "猟") + strObj.selfReplace("獸", "獣") + strObj.selfReplace("獻", "献") + strObj.selfReplace("產", "産") + strObj.selfReplace("畫", "画") + strObj.selfReplace("當", "当") + strObj.selfReplace("疊", "畳") + strObj.selfReplace("疎", "疏") + strObj.selfReplace("痹", "痺") + strObj.selfReplace("瘦", "痩") + strObj.selfReplace("癡", "痴") + strObj.selfReplace("發", "発") + strObj.selfReplace("皋", "皐") + strObj.selfReplace("盜", "盗") + strObj.selfReplace("盡", "尽") + strObj.selfReplace("碎", "砕") + strObj.selfReplace("祕", "秘") + strObj.selfReplace("祿", "禄") + strObj.selfReplace("禪", "禅") + strObj.selfReplace("禮", "礼") + strObj.selfReplace("禱", "祷") + strObj.selfReplace("稅", "税") + strObj.selfReplace("稱", "称") + strObj.selfReplace("稻", "稲") + strObj.selfReplace("穎", "頴") + strObj.selfReplace("穗", "穂") + strObj.selfReplace("穩", "穏") + strObj.selfReplace("穰", "穣") + strObj.selfReplace("竃", "竈") + strObj.selfReplace("竊", "窃") + strObj.selfReplace("粹", "粋") + strObj.selfReplace("糉", "粽") + strObj.selfReplace("絕", "絶") + strObj.selfReplace("經", "経") + strObj.selfReplace("綠", "緑") + strObj.selfReplace("緖", "緒") + strObj.selfReplace("緣", "縁") + strObj.selfReplace("縣", "県") + strObj.selfReplace("縱", "縦") + strObj.selfReplace("總", "総") + strObj.selfReplace("繋", "繫") + strObj.selfReplace("繡", "繍") + strObj.selfReplace("繩", "縄") + strObj.selfReplace("繪", "絵") + strObj.selfReplace("繼", "継") + strObj.selfReplace("續", "続") + strObj.selfReplace("纔", "才") + strObj.selfReplace("纖", "繊") + strObj.selfReplace("罐", "缶") + strObj.selfReplace("羣", "群") + strObj.selfReplace("聯", "連") + strObj.selfReplace("聰", "聡") + strObj.selfReplace("聲", "声") + strObj.selfReplace("聽", "聴") + strObj.selfReplace("肅", "粛") + strObj.selfReplace("脣", "唇") + strObj.selfReplace("脫", "脱") + strObj.selfReplace("腦", "脳") + strObj.selfReplace("腳", "脚") + strObj.selfReplace("膽", "胆") + strObj.selfReplace("臟", "臓") + strObj.selfReplace("臺", "台") + strObj.selfReplace("與", "与") + strObj.selfReplace("舉", "挙") + strObj.selfReplace("舊", "旧") + strObj.selfReplace("舍", "舎") + strObj.selfReplace("荔", "茘") + strObj.selfReplace("莊", "荘") + strObj.selfReplace("莖", "茎") + strObj.selfReplace("菸", "煙") + strObj.selfReplace("萊", "莱") + strObj.selfReplace("萬", "万") + strObj.selfReplace("蔣", "蒋") + strObj.selfReplace("蔥", "葱") + strObj.selfReplace("薰", "薫") + strObj.selfReplace("藏", "蔵") + strObj.selfReplace("藥", "薬") + strObj.selfReplace("蘆", "芦") + strObj.selfReplace("處", "処") + strObj.selfReplace("虛", "虚") + strObj.selfReplace("號", "号") + strObj.selfReplace("螢", "蛍") + strObj.selfReplace("蟲", "虫") + strObj.selfReplace("蠟", "蝋") + strObj.selfReplace("蠶", "蚕") + strObj.selfReplace("蠻", "蛮") + strObj.selfReplace("裝", "装") + strObj.selfReplace("覺", "覚") + strObj.selfReplace("覽", "覧") + strObj.selfReplace("觀", "観") + strObj.selfReplace("觸", "触") + strObj.selfReplace("說", "説") + strObj.selfReplace("謠", "謡") + strObj.selfReplace("證", "証") + strObj.selfReplace("譯", "訳") + strObj.selfReplace("譽", "誉") + strObj.selfReplace("讀", "読") + strObj.selfReplace("變", "変") + strObj.selfReplace("讓", "譲") + strObj.selfReplace("豐", "豊") + strObj.selfReplace("豫", "予") + strObj.selfReplace("貓", "猫") + strObj.selfReplace("貳", "弐") + strObj.selfReplace("賣", "売") + strObj.selfReplace("賴", "頼") + strObj.selfReplace("贊", "賛") + strObj.selfReplace("贗", "贋") + strObj.selfReplace("踐", "践") + strObj.selfReplace("輕", "軽") + strObj.selfReplace("輛", "輌") + strObj.selfReplace("轉", "転") + strObj.selfReplace("辭", "辞") + strObj.selfReplace("遞", "逓") + strObj.selfReplace("遥", "遙") + strObj.selfReplace("遲", "遅") + strObj.selfReplace("邊", "辺") + strObj.selfReplace("鄉", "郷") + strObj.selfReplace("酢", "醋") + strObj.selfReplace("醉", "酔") + strObj.selfReplace("醗", "醱") + strObj.selfReplace("醫", "医") + strObj.selfReplace("醬", "醤") + strObj.selfReplace("釀", "醸") + strObj.selfReplace("釋", "釈") + strObj.selfReplace("鋪", "舗") + strObj.selfReplace("錄", "録") + strObj.selfReplace("錢", "銭") + strObj.selfReplace("鍊", "錬") + strObj.selfReplace("鐵", "鉄") + strObj.selfReplace("鑄", "鋳") + strObj.selfReplace("鑛", "鉱") + strObj.selfReplace("閱", "閲") + strObj.selfReplace("關", "関") + strObj.selfReplace("陷", "陥") + strObj.selfReplace("隨", "随") + strObj.selfReplace("險", "険") + strObj.selfReplace("隱", "隠") + strObj.selfReplace("雙", "双") + strObj.selfReplace("雜", "雑") + strObj.selfReplace("雞", "鶏") + strObj.selfReplace("霸", "覇") + strObj.selfReplace("靈", "霊") + strObj.selfReplace("靜", "静") + strObj.selfReplace("顏", "顔") + strObj.selfReplace("顯", "顕") + strObj.selfReplace("餘", "余") + strObj.selfReplace("騷", "騒") + strObj.selfReplace("驅", "駆") + strObj.selfReplace("驗", "験") + strObj.selfReplace("驛", "駅") + strObj.selfReplace("髓", "髄") + strObj.selfReplace("體", "体") + strObj.selfReplace("髮", "髪") + strObj.selfReplace("鬥", "闘") + strObj.selfReplace("鱉", "鼈") + strObj.selfReplace("鷗", "鴎") + strObj.selfReplace("鹼", "鹸") + strObj.selfReplace("鹽", "塩") + strObj.selfReplace("麥", "麦") + strObj.selfReplace("麪", "麺") + strObj.selfReplace("麴", "麹") + strObj.selfReplace("黃", "黄") + strObj.selfReplace("黑", "黒") + strObj.selfReplace("默", "黙") + strObj.selfReplace("點", "点") + strObj.selfReplace("黨", "党") + strObj.selfReplace("齊", "斉") + strObj.selfReplace("齋", "斎") + strObj.selfReplace("齒", "歯") + strObj.selfReplace("齡", "齢") + strObj.selfReplace("龍", "竜") + strObj.selfReplace("龜", "亀") + strObj.selfReplace("叮嚀", "丁寧") + strObj.selfReplace("鄭重", "丁重") + strObj.selfReplace("輿論", "世論") + strObj.selfReplace("唖鈴", "亜鈴") + strObj.selfReplace("交叉", "交差") + strObj.selfReplace("饗宴", "供宴") + strObj.selfReplace("駿馬", "俊馬") + strObj.selfReplace("堡塁", "保塁") + strObj.selfReplace("扁平", "偏平") + strObj.selfReplace("碇泊", "停泊") + strObj.selfReplace("優駿", "優俊") + strObj.selfReplace("尖兵", "先兵") + strObj.selfReplace("尖鋭", "先鋭") + strObj.selfReplace("共軛", "共役") + strObj.selfReplace("饒舌", "冗舌") + strObj.selfReplace("兇器", "凶器") + strObj.selfReplace("鑿岩", "削岩") + strObj.selfReplace("庖丁", "包丁") + strObj.selfReplace("繃帯", "包帯") + strObj.selfReplace("区劃", "区画") + strObj.selfReplace("儼然", "厳然") + strObj.selfReplace("友誼", "友宜") + strObj.selfReplace("叛乱", "反乱") + strObj.selfReplace("蒐集", "収集") + strObj.selfReplace("抒情", "叙情") + strObj.selfReplace("擡頭", "台頭") + strObj.selfReplace("合弁", "合弁") + strObj.selfReplace("歎願", "嘆願") + strObj.selfReplace("廻転", "回転") + strObj.selfReplace("回游", "回遊") + strObj.selfReplace("捧持", "奉持") + strObj.selfReplace("萎縮", "委縮") + strObj.selfReplace("輾転", "展転") + strObj.selfReplace("稀少", "希少") + strObj.selfReplace("眩惑", "幻惑") + strObj.selfReplace("広汎", "広範") + strObj.selfReplace("曠野", "広野") + strObj.selfReplace("廃墟", "廃虚") + strObj.selfReplace("弁当", "弁当") + strObj.selfReplace("弁膜", "弁膜") + strObj.selfReplace("弁護", "弁護") + strObj.selfReplace("辮髪", "弁髪") + strObj.selfReplace("絃歌", "弦歌") + strObj.selfReplace("恩誼", "恩義") + strObj.selfReplace("意嚮", "意向") + strObj.selfReplace("臆断", "憶断") + strObj.selfReplace("臆病", "憶病") + strObj.selfReplace("戦歿", "戦没") + strObj.selfReplace("煽情", "扇情") + strObj.selfReplace("手帖", "手帳") + strObj.selfReplace("伎倆", "技量") + strObj.selfReplace("抜萃", "抜粋") + strObj.selfReplace("披瀝", "披歴") + strObj.selfReplace("牴触", "抵触") + strObj.selfReplace("抽籤", "抽選") + strObj.selfReplace("勾引", "拘引") + strObj.selfReplace("醵出", "拠出") + strObj.selfReplace("醵金", "拠金") + strObj.selfReplace("掘鑿", "掘削") + strObj.selfReplace("扣除", "控除") + strObj.selfReplace("掩護", "援護") + strObj.selfReplace("抛棄", "放棄") + strObj.selfReplace("撒水", "散水") + strObj.selfReplace("敬虔", "敬謙") + strObj.selfReplace("敷衍", "敷延") + strObj.selfReplace("断乎", "断固") + strObj.selfReplace("簇生", "族生") + strObj.selfReplace("陞叙", "昇叙") + strObj.selfReplace("煖房", "暖房") + strObj.selfReplace("暗誦", "暗唱") + strObj.selfReplace("闇夜", "暗夜") + strObj.selfReplace("曝露", "暴露") + strObj.selfReplace("涸渇", "枯渇") + strObj.selfReplace("恰好", "格好") + strObj.selfReplace("恰幅", "格幅") + strObj.selfReplace("毀損", "棄損") + strObj.selfReplace("摸索", "模索") + strObj.selfReplace("欠欠", "欠缺") + strObj.selfReplace("屍体", "死体") + strObj.selfReplace("臀部", "殿部") + strObj.selfReplace("拇指", "母指") + strObj.selfReplace("気魄", "気迫") + strObj.selfReplace("訣別", "決別") + strObj.selfReplace("決潰", "決壊") + strObj.selfReplace("沈澱", "沈殿") + strObj.selfReplace("波瀾", "波乱") + strObj.selfReplace("註釈", "注釈") + strObj.selfReplace("洗滌", "洗濯") + strObj.selfReplace("活潑", "活発") + strObj.selfReplace("滲透", "浸透") + strObj.selfReplace("浸蝕", "浸食") + strObj.selfReplace("銷却", "消却") + strObj.selfReplace("渾然", "混然") + strObj.selfReplace("弯曲", "湾曲") + strObj.selfReplace("熔接", "溶接") + strObj.selfReplace("漁撈", "漁労") + strObj.selfReplace("飄然", "漂然") + strObj.selfReplace("激昂", "激高") + strObj.selfReplace("火焔", "火炎") + strObj.selfReplace("焦躁", "焦燥") + strObj.selfReplace("斑点", "班点") + strObj.selfReplace("溜飲", "留飲") + strObj.selfReplace("掠奪", "略奪") + strObj.selfReplace("疏通", "疎通") + strObj.selfReplace("醱酵", "発酵") + strObj.selfReplace("白堊", "白亜") + strObj.selfReplace("相剋", "相克") + strObj.selfReplace("智慧", "知恵") + strObj.selfReplace("破毀", "破棄") + strObj.selfReplace("確乎", "確固") + strObj.selfReplace("禁錮", "禁固") + strObj.selfReplace("符牒", "符丁") + strObj.selfReplace("扮装", "粉装") + strObj.selfReplace("紫斑", "紫班") + strObj.selfReplace("終熄", "終息") + strObj.selfReplace("綜合", "総合") + strObj.selfReplace("編輯", "編集") + strObj.selfReplace("義捐", "義援") + strObj.selfReplace("肝腎", "肝心") + strObj.selfReplace("悖徳", "背徳") + strObj.selfReplace("脈搏", "脈拍") + strObj.selfReplace("膨脹", "膨張") + strObj.selfReplace("芳醇", "芳純") + strObj.selfReplace("叡智", "英知") + strObj.selfReplace("蒸溜", "蒸留") + strObj.selfReplace("燻蒸", "薫蒸") + strObj.selfReplace("燻製", "薫製") + strObj.selfReplace("衣裳", "衣装") + strObj.selfReplace("衰頽", "衰退") + strObj.selfReplace("悠然", "裕然") + strObj.selfReplace("輔佐", "補佐") + strObj.selfReplace("訓誡", "訓戒") + strObj.selfReplace("試煉", "試練") + strObj.selfReplace("詭弁", "詭弁") + strObj.selfReplace("媾和", "講和") + strObj.selfReplace("象嵌", "象眼") + strObj.selfReplace("貫禄", "貫録") + strObj.selfReplace("買弁", "買弁") + strObj.selfReplace("讚辞", "賛辞") + strObj.selfReplace("蹈襲", "踏襲") + strObj.selfReplace("車両", "車両") + strObj.selfReplace("顛倒", "転倒") + strObj.selfReplace("輪廓", "輪郭") + strObj.selfReplace("褪色", "退色") + strObj.selfReplace("杜絶", "途絶") + strObj.selfReplace("連繫", "連係") + strObj.selfReplace("連合", "連合") + strObj.selfReplace("銓衡", "選考") + strObj.selfReplace("醋酸", "酢酸") + strObj.selfReplace("野鄙", "野卑") + strObj.selfReplace("礦石", "鉱石") + strObj.selfReplace("間歇", "間欠") + strObj.selfReplace("函数", "関数") + strObj.selfReplace("防御", "防御") + strObj.selfReplace("嶮岨", "険阻") + strObj.selfReplace("牆壁", "障壁") + strObj.selfReplace("障礙", "障害") + strObj.selfReplace("湮滅", "隠滅") + strObj.selfReplace("聚落", "集落") + strObj.selfReplace("雇傭", "雇用") + strObj.selfReplace("諷喩", "風諭") + strObj.selfReplace("蜚語", "飛語") + strObj.selfReplace("香奠", "香典") + strObj.selfReplace("骨骼", "骨格") + strObj.selfReplace("亢進", "高進") + strObj.selfReplace("鳥瞰", "鳥観") + strObj.selfReplace("一攫", "一獲") + strObj.selfReplace("肩胛", "肩甲") + strObj.selfReplace("箇条", "個条") + strObj.selfReplace("啓動", "起動") + strObj.selfReplace("三叉路", "三差路") + strObj.selfReplace("嬉遊曲", "喜遊曲") + strObj.selfReplace("建蔽率", "建坪率") + strObj.selfReplace("慰藉料", "慰謝料") + strObj.selfReplace("橋頭堡", "橋頭保") + strObj.selfReplace("油槽船", "油送船") + strObj.selfReplace("耕耘機", "耕運機") + return strObj + } } diff --git a/Source/Modules/FileHandlers/FSEventStreamHelper.swift b/Source/Modules/FileHandlers/FSEventStreamHelper.swift index ca7dc052..59948360 100644 --- a/Source/Modules/FileHandlers/FSEventStreamHelper.swift +++ b/Source/Modules/FileHandlers/FSEventStreamHelper.swift @@ -1,92 +1,104 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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 public protocol FSEventStreamHelperDelegate: AnyObject { - func helper(_ helper: FSEventStreamHelper, didReceive events: [FSEventStreamHelper.Event]) + func helper(_ helper: FSEventStreamHelper, didReceive events: [FSEventStreamHelper.Event]) } -public class FSEventStreamHelper : NSObject { +public class FSEventStreamHelper: NSObject { - public struct Event { - var path: String - var flags: FSEventStreamEventFlags - var id: FSEventStreamEventId - } + public struct Event { + var path: String + var flags: FSEventStreamEventFlags + var id: FSEventStreamEventId + } - public let path: String - public let dispatchQueue: DispatchQueue - public weak var delegate: FSEventStreamHelperDelegate? + public let path: String + public let dispatchQueue: DispatchQueue + public weak var delegate: FSEventStreamHelperDelegate? - @objc public init(path: String, queue: DispatchQueue) { - self.path = path - self.dispatchQueue = queue - } + @objc public init(path: String, queue: DispatchQueue) { + self.path = path + self.dispatchQueue = queue + } - private var stream: FSEventStreamRef? = nil + private var stream: FSEventStreamRef? = nil - public func start() -> Bool { - if stream != nil { - return false - } - var context = FSEventStreamContext() - context.info = Unmanaged.passUnretained(self).toOpaque() - guard let stream = FSEventStreamCreate(nil, { - (stream, clientCallBackInfo, eventCount, eventPaths, eventFlags, eventIds) in - let helper = Unmanaged.fromOpaque(clientCallBackInfo!).takeUnretainedValue() - let pathsBase = eventPaths.assumingMemoryBound(to: UnsafePointer.self) - let pathsPtr = UnsafeBufferPointer(start: pathsBase, count: eventCount) - let flagsPtr = UnsafeBufferPointer(start: eventFlags, count: eventCount) - let eventIDsPtr = UnsafeBufferPointer(start: eventIds, count: eventCount) - let events = (0.. Bool { + if stream != nil { + return false + } + var context = FSEventStreamContext() + context.info = Unmanaged.passUnretained(self).toOpaque() + guard + let stream = FSEventStreamCreate( + nil, + { + (stream, clientCallBackInfo, eventCount, eventPaths, eventFlags, eventIds) in + let helper = Unmanaged.fromOpaque(clientCallBackInfo!) + .takeUnretainedValue() + let pathsBase = eventPaths.assumingMemoryBound(to: UnsafePointer.self) + let pathsPtr = UnsafeBufferPointer(start: pathsBase, count: eventCount) + let flagsPtr = UnsafeBufferPointer(start: eventFlags, count: eventCount) + let eventIDsPtr = UnsafeBufferPointer(start: eventIds, count: eventCount) + let events = (0.. Bool { - if #available(macOS 10.15, *) { - let appearanceDescription = NSApplication.shared.effectiveAppearance.debugDescription.lowercased() - if appearanceDescription.contains("dark") { - return true - } - } else if #available(macOS 10.14, *) { - if let appleInterfaceStyle = UserDefaults.standard.object(forKey: "AppleInterfaceStyle") as? String { - if appleInterfaceStyle.lowercased().contains("dark") { - return true - } - } - } - return false - } + // MARK: - System Dark Mode Status Detector. + @objc static func isDarkMode() -> Bool { + if #available(macOS 10.15, *) { + let appearanceDescription = NSApplication.shared.effectiveAppearance.debugDescription + .lowercased() + if appearanceDescription.contains("dark") { + return true + } + } else if #available(macOS 10.14, *) { + if let appleInterfaceStyle = UserDefaults.standard.object(forKey: "AppleInterfaceStyle") + as? String + { + if appleInterfaceStyle.lowercased().contains("dark") { + return true + } + } + } + return false + } - // MARK: - Trash a file if it exists. - @discardableResult static func trashTargetIfExists(_ path: String) -> Bool { - do { - if FileManager.default.fileExists(atPath: path) { - // 塞入垃圾桶 - try FileManager.default.trashItem(at: URL(fileURLWithPath: path), resultingItemURL: nil) - } else { - NSLog("Item doesn't exist: \(path)") - } - } catch let error as NSError { - NSLog("Failed from removing this object: \(path) || Error: \(error)") - return false - } - return true - } - // MARK: - Uninstalling the input method. - @discardableResult static func uninstall(isSudo: Bool = false, selfKill: Bool = true) -> Int32 { - // 輸入法自毀處理。這裡不用「Bundle.main.bundleURL」是為了方便使用者以 sudo 身分來移除被錯誤安裝到系統目錄內的輸入法。 - guard let bundleID = Bundle.main.bundleIdentifier else { - NSLog("Failed to ensure the bundle identifier.") - return -1 - } + // MARK: - Trash a file if it exists. + @discardableResult static func trashTargetIfExists(_ path: String) -> Bool { + do { + if FileManager.default.fileExists(atPath: path) { + // 塞入垃圾桶 + try FileManager.default.trashItem( + at: URL(fileURLWithPath: path), resultingItemURL: nil) + } else { + NSLog("Item doesn't exist: \(path)") + } + } catch let error as NSError { + NSLog("Failed from removing this object: \(path) || Error: \(error)") + return false + } + return true + } - let kTargetBin = "vChewing" - let kTargetBundle = "/vChewing.app" - let pathLibrary = isSudo ? "/Library" : FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask)[0].path - let pathIMELibrary = isSudo ? "/Library/Input Methods" : FileManager.default.urls(for: .inputMethodsDirectory, in: .userDomainMask)[0].path - let pathUnitKeyboardLayouts = "/Keyboard Layouts" - let arrKeyLayoutFiles = ["/vChewing ETen.keylayout", "/vChewingKeyLayout.bundle", "/vChewing MiTAC.keylayout", "/vChewing IBM.keylayout", "/vChewing FakeSeigyou.keylayout", "/vChewing Dachen.keylayout"] + // MARK: - Uninstall the input method. + @discardableResult static func uninstall(isSudo: Bool = false, selfKill: Bool = true) -> Int32 { + // 輸入法自毀處理。這裡不用「Bundle.main.bundleURL」是為了方便使用者以 sudo 身分來移除被錯誤安裝到系統目錄內的輸入法。 + guard let bundleID = Bundle.main.bundleIdentifier else { + NSLog("Failed to ensure the bundle identifier.") + return -1 + } - // 先移除各種鍵盤佈局。 - for objPath in arrKeyLayoutFiles { - let objFullPath = pathLibrary + pathUnitKeyboardLayouts + objPath - if !IME.trashTargetIfExists(objFullPath) { return -1 } - } - if CommandLine.arguments.count > 2 && CommandLine.arguments[2] == "--all" && CommandLine.arguments[1] == "uninstall" { - // 再處理是否需要移除放在預設使用者資料夾內的檔案的情況。 - // 如果使用者有在輸入法偏好設定內將該目錄改到別的地方(而不是用 symbol link)的話,則不處理。 - // 目前暫時無法應對 symbol link 的情況。 - IME.trashTargetIfExists(mgrLangModel.dataFolderPath(isDefaultFolder: true)) - IME.trashTargetIfExists(pathLibrary + "/Preferences/" + bundleID + ".plist") // 之後移除 App 偏好設定 - } - if !IME.trashTargetIfExists(pathIMELibrary + kTargetBundle) { return -1 } // 最後移除 App 自身 - // 幹掉殘留在記憶體內的執行緒。 - if selfKill { - let killTask = Process() - killTask.launchPath = "/usr/bin/killall" - killTask.arguments = ["-9", kTargetBin] - killTask.launch() - killTask.waitUntilExit() - } - return 0 - } + let kTargetBin = "vChewing" + let kTargetBundle = "/vChewing.app" + let pathLibrary = + isSudo + ? "/Library" + : FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask)[0].path + let pathIMELibrary = + isSudo + ? "/Library/Input Methods" + : FileManager.default.urls(for: .inputMethodsDirectory, in: .userDomainMask)[0].path + let pathUnitKeyboardLayouts = "/Keyboard Layouts" + let arrKeyLayoutFiles = [ + "/vChewing ETen.keylayout", "/vChewingKeyLayout.bundle", "/vChewing MiTAC.keylayout", + "/vChewing IBM.keylayout", "/vChewing FakeSeigyou.keylayout", + "/vChewing Dachen.keylayout", + ] - // MARK: - Registering the input method. - @discardableResult static func registerInputMethod() -> Int32 { - guard let bundleID = Bundle.main.bundleIdentifier else { - return -1 - } - let bundleUrl = Bundle.main.bundleURL - var maybeInputSource = InputSourceHelper.inputSource(for: bundleID) + // 先移除各種鍵盤佈局。 + for objPath in arrKeyLayoutFiles { + let objFullPath = pathLibrary + pathUnitKeyboardLayouts + objPath + if !IME.trashTargetIfExists(objFullPath) { return -1 } + } + if CommandLine.arguments.count > 2 && CommandLine.arguments[2] == "--all" + && CommandLine.arguments[1] == "uninstall" + { + // 再處理是否需要移除放在預設使用者資料夾內的檔案的情況。 + // 如果使用者有在輸入法偏好設定內將該目錄改到別的地方(而不是用 symbol link)的話,則不處理。 + // 目前暫時無法應對 symbol link 的情況。 + IME.trashTargetIfExists(mgrLangModel.dataFolderPath(isDefaultFolder: true)) + IME.trashTargetIfExists(pathLibrary + "/Preferences/" + bundleID + ".plist") // 之後移除 App 偏好設定 + } + if !IME.trashTargetIfExists(pathIMELibrary + kTargetBundle) { return -1 } // 最後移除 App 自身 + // 幹掉殘留在記憶體內的執行緒。 + if selfKill { + let killTask = Process() + killTask.launchPath = "/usr/bin/killall" + killTask.arguments = ["-9", kTargetBin] + killTask.launch() + killTask.waitUntilExit() + } + return 0 + } - if maybeInputSource == nil { - NSLog("Registering input source \(bundleID) at \(bundleUrl.absoluteString)"); - // then register - let status = InputSourceHelper.registerTnputSource(at: bundleUrl) + // MARK: - Registering the input method. + @discardableResult static func registerInputMethod() -> Int32 { + guard let bundleID = Bundle.main.bundleIdentifier else { + return -1 + } + let bundleUrl = Bundle.main.bundleURL + var maybeInputSource = InputSourceHelper.inputSource(for: bundleID) - if !status { - NSLog("Fatal error: Cannot register input source \(bundleID) at \(bundleUrl.absoluteString).") - return -1 - } + if maybeInputSource == nil { + NSLog("Registering input source \(bundleID) at \(bundleUrl.absoluteString)") + // then register + let status = InputSourceHelper.registerTnputSource(at: bundleUrl) - maybeInputSource = InputSourceHelper.inputSource(for: bundleID) - } + if !status { + NSLog( + "Fatal error: Cannot register input source \(bundleID) at \(bundleUrl.absoluteString)." + ) + return -1 + } - guard let inputSource = maybeInputSource else { - NSLog("Fatal error: Cannot find input source \(bundleID) after registration.") - return -1 - } + maybeInputSource = InputSourceHelper.inputSource(for: bundleID) + } - if !InputSourceHelper.inputSourceEnabled(for: inputSource) { - NSLog("Enabling input source \(bundleID) at \(bundleUrl.absoluteString).") - let status = InputSourceHelper.enable(inputSource: inputSource) - if !status { - NSLog("Fatal error: Cannot enable input source \(bundleID).") - return -1 - } - if !InputSourceHelper.inputSourceEnabled(for: inputSource) { - NSLog("Fatal error: Cannot enable input source \(bundleID).") - return -1 - } - } + guard let inputSource = maybeInputSource else { + NSLog("Fatal error: Cannot find input source \(bundleID) after registration.") + return -1 + } - if CommandLine.arguments.count > 2 && CommandLine.arguments[2] == "--all" { - let enabled = InputSourceHelper.enableAllInputMode(for: bundleID) - NSLog(enabled ? "All input sources enabled for \(bundleID)" : "Cannot enable all input sources for \(bundleID), but this is ignored") - } - return 0 - } + if !InputSourceHelper.inputSourceEnabled(for: inputSource) { + NSLog("Enabling input source \(bundleID) at \(bundleUrl.absoluteString).") + let status = InputSourceHelper.enable(inputSource: inputSource) + if !status { + NSLog("Fatal error: Cannot enable input source \(bundleID).") + return -1 + } + if !InputSourceHelper.inputSourceEnabled(for: inputSource) { + NSLog("Fatal error: Cannot enable input source \(bundleID).") + return -1 + } + } + if CommandLine.arguments.count > 2 && CommandLine.arguments[2] == "--all" { + let enabled = InputSourceHelper.enableAllInputMode(for: bundleID) + NSLog( + enabled + ? "All input sources enabled for \(bundleID)" + : "Cannot enable all input sources for \(bundleID), but this is ignored") + } + return 0 + } } diff --git a/Source/Modules/IMEModules/InputSourceHelper.swift b/Source/Modules/IMEModules/InputSourceHelper.swift index 37429f8a..f045d4cf 100644 --- a/Source/Modules/IMEModules/InputSourceHelper.swift +++ b/Source/Modules/IMEModules/InputSourceHelper.swift @@ -1,127 +1,140 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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 Carbon +import Cocoa public class InputSourceHelper: NSObject { - @available(*, unavailable) - public override init() { - super.init() - } + @available(*, unavailable) + public override init() { + super.init() + } - public static func allInstalledInputSources() -> [TISInputSource] { - TISCreateInputSourceList(nil, true).takeRetainedValue() as! [TISInputSource] - } + public static func allInstalledInputSources() -> [TISInputSource] { + TISCreateInputSourceList(nil, true).takeRetainedValue() as! [TISInputSource] + } - @objc(inputSourceForProperty:stringValue:) - public static func inputSource(for propertyKey: CFString, stringValue: String) -> TISInputSource? { - let stringID = CFStringGetTypeID() - for source in allInstalledInputSources() { - if let propertyPtr = TISGetInputSourceProperty(source, propertyKey) { - let property = Unmanaged.fromOpaque(propertyPtr).takeUnretainedValue() - let typeID = CFGetTypeID(property) - if typeID != stringID { - continue - } - if stringValue == property as? String { - return source - } - } - } - return nil - } + @objc(inputSourceForProperty:stringValue:) + public static func inputSource(for propertyKey: CFString, stringValue: String) + -> TISInputSource? + { + let stringID = CFStringGetTypeID() + for source in allInstalledInputSources() { + if let propertyPtr = TISGetInputSourceProperty(source, propertyKey) { + let property = Unmanaged.fromOpaque(propertyPtr).takeUnretainedValue() + let typeID = CFGetTypeID(property) + if typeID != stringID { + continue + } + if stringValue == property as? String { + return source + } + } + } + return nil + } - @objc(inputSourceForInputSourceID:) - public static func inputSource(for sourceID: String) -> TISInputSource? { - inputSource(for: kTISPropertyInputSourceID, stringValue: sourceID) - } + @objc(inputSourceForInputSourceID:) + public static func inputSource(for sourceID: String) -> TISInputSource? { + inputSource(for: kTISPropertyInputSourceID, stringValue: sourceID) + } - @objc(inputSourceEnabled:) - public static func inputSourceEnabled(for source: TISInputSource) -> Bool { - if let valuePts = TISGetInputSourceProperty(source, kTISPropertyInputSourceIsEnabled) { - let value = Unmanaged.fromOpaque(valuePts).takeUnretainedValue() - return value == kCFBooleanTrue - } - return false - } + @objc(inputSourceEnabled:) + public static func inputSourceEnabled(for source: TISInputSource) -> Bool { + if let valuePts = TISGetInputSourceProperty(source, kTISPropertyInputSourceIsEnabled) { + let value = Unmanaged.fromOpaque(valuePts).takeUnretainedValue() + return value == kCFBooleanTrue + } + return false + } - @objc(enableInputSource:) - public static func enable(inputSource: TISInputSource) -> Bool { - let status = TISEnableInputSource(inputSource) - return status == noErr - } + @objc(enableInputSource:) + public static func enable(inputSource: TISInputSource) -> Bool { + let status = TISEnableInputSource(inputSource) + return status == noErr + } - @objc(enableAllInputModesForInputSourceBundleID:) - public static func enableAllInputMode(for inputSourceBundleD: String) -> Bool { - var enabled = false - for source in allInstalledInputSources() { - guard let bundleIDPtr = TISGetInputSourceProperty(source, kTISPropertyBundleID), - let _ = TISGetInputSourceProperty(source, kTISPropertyInputModeID) else { - continue - } - let bundleID = Unmanaged.fromOpaque(bundleIDPtr).takeUnretainedValue() - if String(bundleID) == inputSourceBundleD { - let modeEnabled = self.enable(inputSource: source) - if !modeEnabled { - return false - } - enabled = true - } - } + @objc(enableAllInputModesForInputSourceBundleID:) + public static func enableAllInputMode(for inputSourceBundleD: String) -> Bool { + var enabled = false + for source in allInstalledInputSources() { + guard let bundleIDPtr = TISGetInputSourceProperty(source, kTISPropertyBundleID), + let _ = TISGetInputSourceProperty(source, kTISPropertyInputModeID) + else { + continue + } + let bundleID = Unmanaged.fromOpaque(bundleIDPtr).takeUnretainedValue() + if String(bundleID) == inputSourceBundleD { + let modeEnabled = self.enable(inputSource: source) + if !modeEnabled { + return false + } + enabled = true + } + } - return enabled - } + return enabled + } - @objc(enableInputMode:forInputSourceBundleID:) - public static func enable(inputMode modeID: String, for bundleID: String) -> Bool { - for source in allInstalledInputSources() { - guard let bundleIDPtr = TISGetInputSourceProperty(source, kTISPropertyBundleID), - let modePtr = TISGetInputSourceProperty(source, kTISPropertyInputModeID) else { - continue - } - let inputsSourceBundleID = Unmanaged.fromOpaque(bundleIDPtr).takeUnretainedValue() - let inputsSourceModeID = Unmanaged.fromOpaque(modePtr).takeUnretainedValue() - if modeID == String(inputsSourceModeID) && bundleID == String(inputsSourceBundleID) { - let enabled = enable(inputSource: source) - print("Attempt to enable input source of mode: \(modeID), bundle ID: \(bundleID), result: \(enabled)") - return enabled - } + @objc(enableInputMode:forInputSourceBundleID:) + public static func enable(inputMode modeID: String, for bundleID: String) -> Bool { + for source in allInstalledInputSources() { + guard let bundleIDPtr = TISGetInputSourceProperty(source, kTISPropertyBundleID), + let modePtr = TISGetInputSourceProperty(source, kTISPropertyInputModeID) + else { + continue + } + let inputsSourceBundleID = Unmanaged.fromOpaque(bundleIDPtr) + .takeUnretainedValue() + let inputsSourceModeID = Unmanaged.fromOpaque(modePtr).takeUnretainedValue() + if modeID == String(inputsSourceModeID) && bundleID == String(inputsSourceBundleID) { + let enabled = enable(inputSource: source) + print( + "Attempt to enable input source of mode: \(modeID), bundle ID: \(bundleID), result: \(enabled)" + ) + return enabled + } - } - print("Failed to find any matching input source of mode: \(modeID), bundle ID: \(bundleID)") - return false + } + print("Failed to find any matching input source of mode: \(modeID), bundle ID: \(bundleID)") + return false - } + } - @objc(disableInputSource:) - public static func disable(inputSource: TISInputSource) -> Bool { - let status = TISDisableInputSource(inputSource) - return status == noErr - } + @objc(disableInputSource:) + public static func disable(inputSource: TISInputSource) -> Bool { + let status = TISDisableInputSource(inputSource) + return status == noErr + } - @objc(registerInputSource:) - public static func registerTnputSource(at url: URL) -> Bool { - let status = TISRegisterInputSource(url as CFURL) - return status == noErr - } + @objc(registerInputSource:) + public static func registerTnputSource(at url: URL) -> Bool { + let status = TISRegisterInputSource(url as CFURL) + return status == noErr + } } - diff --git a/Source/Modules/IMEModules/ctlInputMethod.swift b/Source/Modules/IMEModules/ctlInputMethod.swift index 9500a800..4bfc6675 100644 --- a/Source/Modules/IMEModules/ctlInputMethod.swift +++ b/Source/Modules/IMEModules/ctlInputMethod.swift @@ -1,344 +1,444 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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 -private extension Bool { - var state: NSControl.StateValue { - self ? .on : .off - } +extension Bool { + fileprivate var state: NSControl.StateValue { + self ? .on : .off + } } private let kMinKeyLabelSize: CGFloat = 10 private var gCurrentCandidateController: CandidateController? -private extension CandidateController { - static let horizontal = HorizontalCandidateController() - static let vertical = VerticalCandidateController() +extension CandidateController { + fileprivate static let horizontal = HorizontalCandidateController() + fileprivate static let vertical = VerticalCandidateController() } @objc(ctlInputMethod) class ctlInputMethod: IMKInputController { - @objc static let kIMEModeCHS = "org.atelierInmu.inputmethod.vChewing.IMECHS"; - @objc static let kIMEModeCHT = "org.atelierInmu.inputmethod.vChewing.IMECHT"; - @objc static let kIMEModeNULL = "org.atelierInmu.inputmethod.vChewing.IMENULL"; - - @objc static var areWeDeleting = false; - - private static let tooltipController = TooltipController() - - // MARK: - - - private var currentCandidateClient: Any? - - private var keyHandler: KeyHandler = KeyHandler() - private var state: InputState = InputState.Empty() - - // 想讓 keyHandler 能夠被外界調查狀態與參數的話,就得對 keyHandler 做常態處理。 - // 這樣 InputState 可以藉由這個 ctlInputMethod 了解到當前的輸入模式是簡體中文還是繁體中文。 - // 然而,要是直接對 keyHandler 做常態處理的話,反而會導致 keyParser 無法協同處理。 - // 所以才需要「currentKeyHandler」這個假 keyHandler。 - // 這個「currentKeyHandler」僅用來讓其他模組知道當前的輸入模式是什麼模式,除此之外別無屌用。 - static var currentKeyHandler: KeyHandler = KeyHandler() - @objc static var currentInputMode = "" - - // MARK: - Keyboard Layout Specifier - - @objc func setKeyLayout() { - let client = client().self as IMKTextInput - client.overrideKeyboard(withKeyboardNamed: mgrPrefs.basisKeyboardLayout) - } - - // 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 optionKeyPressed = NSEvent.modifierFlags.contains(.option) - - let menu = NSMenu(title: "Input Method Menu") - - let useSCPCTypingModeItem = menu.addItem(withTitle: NSLocalizedString("Per-Char Select Mode", comment: ""), action: #selector(toggleSCPCTypingMode(_:)), keyEquivalent: "P") - useSCPCTypingModeItem.keyEquivalentModifierMask = [.command, .control] - useSCPCTypingModeItem.state = mgrPrefs.useSCPCTypingMode.state - - let useCNS11643SupportItem = menu.addItem(withTitle: NSLocalizedString("CNS11643 Mode", comment: ""), action: #selector(toggleCNS11643Enabled(_:)), keyEquivalent: "L") - useCNS11643SupportItem.keyEquivalentModifierMask = [.command, .control] - useCNS11643SupportItem.state = mgrPrefs.cns11643Enabled.state - - if keyHandler.inputMode == InputMode.imeModeCHT { - let chineseConversionItem = menu.addItem(withTitle: NSLocalizedString("Force KangXi Writing", comment: ""), action: #selector(toggleChineseConverter(_:)), keyEquivalent: "K") - chineseConversionItem.keyEquivalentModifierMask = [.command, .control] - chineseConversionItem.state = mgrPrefs.chineseConversionEnabled.state - - let shiftJISConversionItem = menu.addItem(withTitle: NSLocalizedString("JIS Shinjitai Output", comment: ""), action: #selector(toggleShiftJISShinjitaiOutput(_:)), keyEquivalent: "J") - shiftJISConversionItem.keyEquivalentModifierMask = [.command, .control] - shiftJISConversionItem.state = mgrPrefs.shiftJISShinjitaiOutputEnabled.state - } - - let halfWidthPunctuationItem = menu.addItem(withTitle: NSLocalizedString("Half-Width Punctuation Mode", comment: ""), action: #selector(toggleHalfWidthPunctuation(_:)), keyEquivalent: "H") - halfWidthPunctuationItem.keyEquivalentModifierMask = [.command, .control] - halfWidthPunctuationItem.state = mgrPrefs.halfWidthPunctuationEnabled.state - - let userAssociatedPhrasesItem = menu.addItem(withTitle: NSLocalizedString("Per-Char Associated Phrases", comment: ""), action: #selector(toggleAssociatedPhrasesEnabled(_:)), keyEquivalent: "O") - userAssociatedPhrasesItem.keyEquivalentModifierMask = [.command, .control] - userAssociatedPhrasesItem.state = mgrPrefs.associatedPhrasesEnabled.state - - if optionKeyPressed { - let phaseReplacementItem = menu.addItem(withTitle: NSLocalizedString("Use Phrase Replacement", comment: ""), action: #selector(togglePhraseReplacement(_:)), keyEquivalent: "") - phaseReplacementItem.state = mgrPrefs.phraseReplacementEnabled.state - - let toggleSymbolInputItem = menu.addItem(withTitle: NSLocalizedString("Symbol & Emoji Input", comment: ""), action: #selector(toggleSymbolEnabled(_:)), keyEquivalent: "") - toggleSymbolInputItem.state = mgrPrefs.symbolInputEnabled.state - } - - menu.addItem(NSMenuItem.separator()) // --------------------- - - menu.addItem(withTitle: NSLocalizedString("Open User Data Folder", comment: ""), action: #selector(openUserDataFolder(_:)), keyEquivalent: "") - menu.addItem(withTitle: NSLocalizedString("Edit User Phrases…", comment: ""), action: #selector(openUserPhrases(_:)), keyEquivalent: "") - - if optionKeyPressed { - menu.addItem(withTitle: NSLocalizedString("Edit Excluded Phrases…", comment: ""), action: #selector(openExcludedPhrases(_:)), keyEquivalent: "") - menu.addItem(withTitle: NSLocalizedString("Edit Phrase Replacement Table…", comment: ""), action: #selector(openPhraseReplacement(_:)), keyEquivalent: "") - menu.addItem(withTitle: NSLocalizedString("Edit Associated Phrases…", comment: ""), action: #selector(openAssociatedPhrases(_:)), keyEquivalent: "") - menu.addItem(withTitle: NSLocalizedString("Edit User Symbol & Emoji Data…", comment: ""), action: #selector(openUserSymbols(_:)), keyEquivalent: "") - } - - if (optionKeyPressed || !mgrPrefs.shouldAutoReloadUserDataFiles) { - menu.addItem(withTitle: NSLocalizedString("Reload User Phrases", comment: ""), action: #selector(reloadUserPhrases(_:)), keyEquivalent: "") - } - - menu.addItem(NSMenuItem.separator()) // --------------------- - - menu.addItem(withTitle: NSLocalizedString("vChewing Preferences…", comment: ""), action: #selector(showPreferences(_:)), keyEquivalent: "") - if !optionKeyPressed { - menu.addItem(withTitle: NSLocalizedString("Check for Updates…", comment: ""), action: #selector(checkForUpdate(_:)), keyEquivalent: "") - } - menu.addItem(withTitle: NSLocalizedString("Reboot vChewing…", comment: ""), action: #selector(selfTerminate(_:)), keyEquivalent: "") - menu.addItem(withTitle: NSLocalizedString("About vChewing…", comment: ""), action: #selector(showAbout(_:)), keyEquivalent: "") - if optionKeyPressed { - menu.addItem(withTitle: NSLocalizedString("Uninstall vChewing…", comment: ""), action: #selector(selfUninstall(_:)), keyEquivalent: "") - } - - // NSMenu 會阻止任何 modified key 相關的訊號傳回輸入法,所以咱們在此重設鍵盤佈局 - setKeyLayout() - return menu - } - - // MARK: - IMKStateSetting protocol methods - - override func activateServer(_ client: Any!) { - UserDefaults.standard.synchronize() - - // Override the keyboard layout to the basic one. - setKeyLayout() - // reset the state - 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!) { - var newInputMode = InputMode(rawValue: value as? String ?? InputMode.imeModeNULL.rawValue) - switch newInputMode { - case InputMode.imeModeCHS: - newInputMode = InputMode.imeModeCHS - case InputMode.imeModeCHT: - newInputMode = InputMode.imeModeCHT - default: - newInputMode = InputMode.imeModeNULL - } - mgrLangModel.loadDataModel(newInputMode) - - // Remember to override the keyboard layout again -- treat this as an activate event. - setKeyLayout() - - if keyHandler.inputMode != newInputMode { - UserDefaults.standard.synchronize() - keyHandler.clear() - keyHandler.inputMode = newInputMode - self.handle(state: .Empty(), client: client) - } - - // 讓外界知道目前的簡繁體輸入模式。 - ctlInputMethod.currentKeyHandler.inputMode = keyHandler.inputMode - } - - // 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 { - - // 這裡仍舊需要判斷 flags。之前使輸入法狀態卡住無法敲漢字的問題已在 KeyHandler 內修復。 - // 這裡不判斷 flags 的話,用方向鍵前後定位光標之後,再次試圖觸發組字區時、反而會在首次按鍵時失敗。 - // 同時注意:必須在 event.type == .flagsChanged 結尾插入 return false, - // 否則,每次處理這種判斷時都會觸發 NSInternalInconsistencyException。 - if event.type == .flagsChanged { - return false - } - - // 準備修飾鍵,用來判定是否需要利用就地新增語彙時的 Enter 鍵來砍詞。 - ctlInputMethod.areWeDeleting = event.modifierFlags.contains([.shift, .command]) - - 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() == "org.atelierInmu.vChewing.vChewingPhraseEditor" { - ctlInputMethod.areWeUsingOurOwnPhraseEditor = true - } else { - ctlInputMethod.areWeUsingOurOwnPhraseEditor = false - } - - let input = keyParser(event: event, isVerticalMode: useVerticalMode) - - let result = keyHandler.handle(input: input, state: state) { newState in - self.handle(state: newState, client: client) - } errorCallback: { - clsSFX.beep() - } - return result - } - - // MARK: - Menu Items - - @objc override func showPreferences(_ sender: Any?) { - (NSApp.delegate as? AppDelegate)?.showPreferences() - NSApp.activate(ignoringOtherApps: true) - } - - @objc func toggleSCPCTypingMode(_ sender: Any?) { - NotifierController.notify(message: String(format: "%@%@%@", NSLocalizedString("Per-Char Select Mode", comment: ""), "\n", mgrPrefs.toggleSCPCTypingModeEnabled() ? NSLocalizedString("NotificationSwitchON", comment: "") : NSLocalizedString("NotificationSwitchOFF", comment: ""))) - } - - @objc func toggleChineseConverter(_ sender: Any?) { - NotifierController.notify(message: String(format: "%@%@%@", NSLocalizedString("Force KangXi Writing", comment: ""), "\n", mgrPrefs.toggleChineseConversionEnabled() ? NSLocalizedString("NotificationSwitchON", comment: "") : NSLocalizedString("NotificationSwitchOFF", comment: ""))) - } - - @objc func toggleShiftJISShinjitaiOutput(_ sender: Any?) { - NotifierController.notify(message: String(format: "%@%@%@", NSLocalizedString("JIS Shinjitai Output", comment: ""), "\n", mgrPrefs.toggleShiftJISShinjitaiOutputEnabled() ? NSLocalizedString("NotificationSwitchON", comment: "") : NSLocalizedString("NotificationSwitchOFF", comment: ""))) - } - - @objc func toggleHalfWidthPunctuation(_ sender: Any?) { - NotifierController.notify(message: String(format: "%@%@%@", NSLocalizedString("Half-Width Punctuation Mode", comment: ""), "\n", mgrPrefs.toggleHalfWidthPunctuationEnabled() ? NSLocalizedString("NotificationSwitchON", comment: "") : NSLocalizedString("NotificationSwitchOFF", comment: ""))) - } - - @objc func toggleCNS11643Enabled(_ sender: Any?) { - NotifierController.notify(message: String(format: "%@%@%@", NSLocalizedString("CNS11643 Mode", comment: ""), "\n", mgrPrefs.toggleCNS11643Enabled() ? NSLocalizedString("NotificationSwitchON", comment: "") : NSLocalizedString("NotificationSwitchOFF", comment: ""))) - } - - @objc func toggleSymbolEnabled(_ sender: Any?) { - NotifierController.notify(message: String(format: "%@%@%@", NSLocalizedString("Symbol & Emoji Input", comment: ""), "\n", mgrPrefs.toggleSymbolInputEnabled() ? NSLocalizedString("NotificationSwitchON", comment: "") : NSLocalizedString("NotificationSwitchOFF", comment: ""))) - } - - @objc func toggleAssociatedPhrasesEnabled(_ sender: Any?) { - NotifierController.notify(message: String(format: "%@%@%@", NSLocalizedString("Per-Char Associated Phrases", comment: ""), "\n", mgrPrefs.toggleAssociatedPhrasesEnabled() ? NSLocalizedString("NotificationSwitchON", comment: "") : NSLocalizedString("NotificationSwitchOFF", comment: ""))) - } - - @objc func togglePhraseReplacement(_ sender: Any?) { - NotifierController.notify(message: String(format: "%@%@%@", NSLocalizedString("Use Phrase Replacement", comment: ""), "\n", mgrPrefs.togglePhraseReplacementEnabled() ? NSLocalizedString("NotificationSwitchON", comment: "") : NSLocalizedString("NotificationSwitchOFF", comment: ""))) - } - - @objc func selfUninstall(_ sender: Any?) { - (NSApp.delegate as? AppDelegate)?.selfUninstall() - } - - @objc func selfTerminate(_ sender: Any?) { - NSApp.terminate(nil) - } - - @objc func checkForUpdate(_ sender: Any?) { - (NSApp.delegate as? AppDelegate)?.checkForUpdate(forced: true) - } - - private func open(userFileAt path: String) { - func checkIfUserFilesExist() -> Bool { - if !mgrLangModel.checkIfUserLanguageModelFilesExist() { - let content = String(format: NSLocalizedString("Please check the permission at \"%@\".", comment: ""), mgrLangModel.dataFolderPath(isDefaultFolder: false)) - ctlNonModalAlertWindow.shared.show(title: NSLocalizedString("Unable to create the user phrase file.", comment: ""), content: content, confirmButtonTitle: NSLocalizedString("OK", comment: ""), cancelButtonTitle: nil, cancelAsDefault: false, delegate: nil) - NSApp.setActivationPolicy(.accessory) - return false - } - return true - } - - if !checkIfUserFilesExist() { - return - } - NSWorkspace.shared.openFile(path, withApplication: "vChewingPhraseEditor") - } - - @objc func openUserPhrases(_ sender: Any?) { - open(userFileAt: mgrLangModel.userPhrasesDataPath(keyHandler.inputMode)) - } - - @objc func openUserDataFolder(_ sender: Any?) { - if !mgrLangModel.checkIfUserDataFolderExists() { - return - } - NSWorkspace.shared.openFile(mgrLangModel.dataFolderPath(isDefaultFolder: false), withApplication: "Finder") - } - - @objc func openExcludedPhrases(_ sender: Any?) { - open(userFileAt: mgrLangModel.excludedPhrasesDataPath(keyHandler.inputMode)) - } - - @objc func openUserSymbols(_ sender: Any?) { - open(userFileAt: mgrLangModel.userSymbolDataPath(keyHandler.inputMode)) - } - - @objc func openPhraseReplacement(_ sender: Any?) { - open(userFileAt: mgrLangModel.phraseReplacementDataPath(keyHandler.inputMode)) - } - - @objc func openAssociatedPhrases(_ sender: Any?) { - open(userFileAt: mgrLangModel.userAssociatedPhrasesDataPath(keyHandler.inputMode)) - } - - @objc func reloadUserPhrases(_ sender: Any?) { - mgrLangModel.loadUserPhrases() - mgrLangModel.loadUserPhraseReplacement() - } - - @objc func showAbout(_ sender: Any?) { - (NSApp.delegate as? AppDelegate)?.showAbout() - NSApp.activate(ignoringOtherApps: true) - } + @objc static let kIMEModeCHS = "org.atelierInmu.inputmethod.vChewing.IMECHS" + @objc static let kIMEModeCHT = "org.atelierInmu.inputmethod.vChewing.IMECHT" + @objc static let kIMEModeNULL = "org.atelierInmu.inputmethod.vChewing.IMENULL" + + @objc static var areWeDeleting = false + + private static let tooltipController = TooltipController() + + // MARK: - + + private var currentCandidateClient: Any? + + private var keyHandler: KeyHandler = KeyHandler() + private var state: InputState = InputState.Empty() + + // 想讓 keyHandler 能夠被外界調查狀態與參數的話,就得對 keyHandler 做常態處理。 + // 這樣 InputState 可以藉由這個 ctlInputMethod 了解到當前的輸入模式是簡體中文還是繁體中文。 + // 然而,要是直接對 keyHandler 做常態處理的話,反而會導致 keyParser 無法協同處理。 + // 所以才需要「currentKeyHandler」這個假 keyHandler。 + // 這個「currentKeyHandler」僅用來讓其他模組知道當前的輸入模式是什麼模式,除此之外別無屌用。 + static var currentKeyHandler: KeyHandler = KeyHandler() + @objc static var currentInputMode = "" + + // MARK: - Keyboard Layout Specifier + + @objc func setKeyLayout() { + let client = client().self as IMKTextInput + client.overrideKeyboard(withKeyboardNamed: mgrPrefs.basisKeyboardLayout) + } + + // 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 optionKeyPressed = NSEvent.modifierFlags.contains(.option) + + let menu = NSMenu(title: "Input Method Menu") + + let useSCPCTypingModeItem = menu.addItem( + withTitle: NSLocalizedString("Per-Char Select Mode", comment: ""), + action: #selector(toggleSCPCTypingMode(_:)), keyEquivalent: "P") + useSCPCTypingModeItem.keyEquivalentModifierMask = [.command, .control] + useSCPCTypingModeItem.state = mgrPrefs.useSCPCTypingMode.state + + let useCNS11643SupportItem = menu.addItem( + withTitle: NSLocalizedString("CNS11643 Mode", comment: ""), + action: #selector(toggleCNS11643Enabled(_:)), keyEquivalent: "L") + useCNS11643SupportItem.keyEquivalentModifierMask = [.command, .control] + useCNS11643SupportItem.state = mgrPrefs.cns11643Enabled.state + + if keyHandler.inputMode == InputMode.imeModeCHT { + let chineseConversionItem = menu.addItem( + withTitle: NSLocalizedString("Force KangXi Writing", comment: ""), + action: #selector(toggleChineseConverter(_:)), keyEquivalent: "K") + chineseConversionItem.keyEquivalentModifierMask = [.command, .control] + chineseConversionItem.state = mgrPrefs.chineseConversionEnabled.state + + let shiftJISConversionItem = menu.addItem( + withTitle: NSLocalizedString("JIS Shinjitai Output", comment: ""), + action: #selector(toggleShiftJISShinjitaiOutput(_:)), keyEquivalent: "J") + shiftJISConversionItem.keyEquivalentModifierMask = [.command, .control] + shiftJISConversionItem.state = mgrPrefs.shiftJISShinjitaiOutputEnabled.state + } + + let halfWidthPunctuationItem = menu.addItem( + withTitle: NSLocalizedString("Half-Width Punctuation Mode", comment: ""), + action: #selector(toggleHalfWidthPunctuation(_:)), keyEquivalent: "H") + halfWidthPunctuationItem.keyEquivalentModifierMask = [.command, .control] + halfWidthPunctuationItem.state = mgrPrefs.halfWidthPunctuationEnabled.state + + let userAssociatedPhrasesItem = menu.addItem( + withTitle: NSLocalizedString("Per-Char Associated Phrases", comment: ""), + action: #selector(toggleAssociatedPhrasesEnabled(_:)), keyEquivalent: "O") + userAssociatedPhrasesItem.keyEquivalentModifierMask = [.command, .control] + userAssociatedPhrasesItem.state = mgrPrefs.associatedPhrasesEnabled.state + + if optionKeyPressed { + let phaseReplacementItem = menu.addItem( + withTitle: NSLocalizedString("Use Phrase Replacement", comment: ""), + action: #selector(togglePhraseReplacement(_:)), keyEquivalent: "") + phaseReplacementItem.state = mgrPrefs.phraseReplacementEnabled.state + + let toggleSymbolInputItem = menu.addItem( + withTitle: NSLocalizedString("Symbol & Emoji Input", comment: ""), + action: #selector(toggleSymbolEnabled(_:)), keyEquivalent: "") + toggleSymbolInputItem.state = mgrPrefs.symbolInputEnabled.state + } + + menu.addItem(NSMenuItem.separator()) // --------------------- + + menu.addItem( + withTitle: NSLocalizedString("Open User Data Folder", comment: ""), + action: #selector(openUserDataFolder(_:)), keyEquivalent: "") + menu.addItem( + withTitle: NSLocalizedString("Edit User Phrases…", comment: ""), + action: #selector(openUserPhrases(_:)), keyEquivalent: "") + + if optionKeyPressed { + menu.addItem( + withTitle: NSLocalizedString("Edit Excluded Phrases…", comment: ""), + action: #selector(openExcludedPhrases(_:)), keyEquivalent: "") + menu.addItem( + withTitle: NSLocalizedString("Edit Phrase Replacement Table…", comment: ""), + action: #selector(openPhraseReplacement(_:)), keyEquivalent: "") + menu.addItem( + withTitle: NSLocalizedString("Edit Associated Phrases…", comment: ""), + action: #selector(openAssociatedPhrases(_:)), keyEquivalent: "") + menu.addItem( + withTitle: NSLocalizedString("Edit User Symbol & Emoji Data…", comment: ""), + action: #selector(openUserSymbols(_:)), keyEquivalent: "") + } + + if optionKeyPressed || !mgrPrefs.shouldAutoReloadUserDataFiles { + menu.addItem( + withTitle: NSLocalizedString("Reload User Phrases", comment: ""), + action: #selector(reloadUserPhrases(_:)), keyEquivalent: "") + } + + menu.addItem(NSMenuItem.separator()) // --------------------- + + menu.addItem( + withTitle: NSLocalizedString("vChewing Preferences…", comment: ""), + action: #selector(showPreferences(_:)), keyEquivalent: "") + if !optionKeyPressed { + menu.addItem( + withTitle: NSLocalizedString("Check for Updates…", comment: ""), + action: #selector(checkForUpdate(_:)), keyEquivalent: "") + } + menu.addItem( + withTitle: NSLocalizedString("Reboot vChewing…", comment: ""), + action: #selector(selfTerminate(_:)), keyEquivalent: "") + menu.addItem( + withTitle: NSLocalizedString("About vChewing…", comment: ""), + action: #selector(showAbout(_:)), keyEquivalent: "") + if optionKeyPressed { + menu.addItem( + withTitle: NSLocalizedString("Uninstall vChewing…", comment: ""), + action: #selector(selfUninstall(_:)), keyEquivalent: "") + } + + // NSMenu 會阻止任何 modified key 相關的訊號傳回輸入法,所以咱們在此重設鍵盤佈局 + setKeyLayout() + return menu + } + + // MARK: - IMKStateSetting protocol methods + + override func activateServer(_ client: Any!) { + UserDefaults.standard.synchronize() + + // Override the keyboard layout to the basic one. + setKeyLayout() + // reset the state + 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!) { + var newInputMode = InputMode(rawValue: value as? String ?? InputMode.imeModeNULL.rawValue) + switch newInputMode { + case InputMode.imeModeCHS: + newInputMode = InputMode.imeModeCHS + case InputMode.imeModeCHT: + newInputMode = InputMode.imeModeCHT + default: + newInputMode = InputMode.imeModeNULL + } + mgrLangModel.loadDataModel(newInputMode) + + // Remember to override the keyboard layout again -- treat this as an activate event. + setKeyLayout() + + if keyHandler.inputMode != newInputMode { + UserDefaults.standard.synchronize() + keyHandler.clear() + keyHandler.inputMode = newInputMode + self.handle(state: .Empty(), client: client) + } + + // 讓外界知道目前的簡繁體輸入模式。 + ctlInputMethod.currentKeyHandler.inputMode = keyHandler.inputMode + } + + // 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 { + + // 這裡仍舊需要判斷 flags。之前使輸入法狀態卡住無法敲漢字的問題已在 KeyHandler 內修復。 + // 這裡不判斷 flags 的話,用方向鍵前後定位光標之後,再次試圖觸發組字區時、反而會在首次按鍵時失敗。 + // 同時注意:必須在 event.type == .flagsChanged 結尾插入 return false, + // 否則,每次處理這種判斷時都會觸發 NSInternalInconsistencyException。 + if event.type == .flagsChanged { + return false + } + + // 準備修飾鍵,用來判定是否需要利用就地新增語彙時的 Enter 鍵來砍詞。 + ctlInputMethod.areWeDeleting = event.modifierFlags.contains([.shift, .command]) + + 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() + == "org.atelierInmu.vChewing.vChewingPhraseEditor" + { + ctlInputMethod.areWeUsingOurOwnPhraseEditor = true + } else { + ctlInputMethod.areWeUsingOurOwnPhraseEditor = false + } + + let input = keyParser(event: event, isVerticalMode: useVerticalMode) + + let result = keyHandler.handle(input: input, state: state) { newState in + self.handle(state: newState, client: client) + } errorCallback: { + clsSFX.beep() + } + return result + } + + // MARK: - Menu Items + + @objc override func showPreferences(_ sender: Any?) { + (NSApp.delegate as? AppDelegate)?.showPreferences() + NSApp.activate(ignoringOtherApps: true) + } + + @objc func toggleSCPCTypingMode(_ sender: Any?) { + NotifierController.notify( + message: String( + format: "%@%@%@", NSLocalizedString("Per-Char Select Mode", comment: ""), "\n", + mgrPrefs.toggleSCPCTypingModeEnabled() + ? NSLocalizedString("NotificationSwitchON", comment: "") + : NSLocalizedString("NotificationSwitchOFF", comment: ""))) + } + + @objc func toggleChineseConverter(_ sender: Any?) { + NotifierController.notify( + message: String( + format: "%@%@%@", NSLocalizedString("Force KangXi Writing", comment: ""), "\n", + mgrPrefs.toggleChineseConversionEnabled() + ? NSLocalizedString("NotificationSwitchON", comment: "") + : NSLocalizedString("NotificationSwitchOFF", comment: ""))) + } + + @objc func toggleShiftJISShinjitaiOutput(_ sender: Any?) { + NotifierController.notify( + message: String( + format: "%@%@%@", NSLocalizedString("JIS Shinjitai Output", comment: ""), "\n", + mgrPrefs.toggleShiftJISShinjitaiOutputEnabled() + ? NSLocalizedString("NotificationSwitchON", comment: "") + : NSLocalizedString("NotificationSwitchOFF", comment: ""))) + } + + @objc func toggleHalfWidthPunctuation(_ sender: Any?) { + NotifierController.notify( + message: String( + format: "%@%@%@", NSLocalizedString("Half-Width Punctuation Mode", comment: ""), + "\n", + mgrPrefs.toggleHalfWidthPunctuationEnabled() + ? NSLocalizedString("NotificationSwitchON", comment: "") + : NSLocalizedString("NotificationSwitchOFF", comment: ""))) + } + + @objc func toggleCNS11643Enabled(_ sender: Any?) { + NotifierController.notify( + message: String( + format: "%@%@%@", NSLocalizedString("CNS11643 Mode", comment: ""), "\n", + mgrPrefs.toggleCNS11643Enabled() + ? NSLocalizedString("NotificationSwitchON", comment: "") + : NSLocalizedString("NotificationSwitchOFF", comment: ""))) + } + + @objc func toggleSymbolEnabled(_ sender: Any?) { + NotifierController.notify( + message: String( + format: "%@%@%@", NSLocalizedString("Symbol & Emoji Input", comment: ""), "\n", + mgrPrefs.toggleSymbolInputEnabled() + ? NSLocalizedString("NotificationSwitchON", comment: "") + : NSLocalizedString("NotificationSwitchOFF", comment: ""))) + } + + @objc func toggleAssociatedPhrasesEnabled(_ sender: Any?) { + NotifierController.notify( + message: String( + format: "%@%@%@", NSLocalizedString("Per-Char Associated Phrases", comment: ""), + "\n", + mgrPrefs.toggleAssociatedPhrasesEnabled() + ? NSLocalizedString("NotificationSwitchON", comment: "") + : NSLocalizedString("NotificationSwitchOFF", comment: ""))) + } + + @objc func togglePhraseReplacement(_ sender: Any?) { + NotifierController.notify( + message: String( + format: "%@%@%@", NSLocalizedString("Use Phrase Replacement", comment: ""), "\n", + mgrPrefs.togglePhraseReplacementEnabled() + ? NSLocalizedString("NotificationSwitchON", comment: "") + : NSLocalizedString("NotificationSwitchOFF", comment: ""))) + } + + @objc func selfUninstall(_ sender: Any?) { + (NSApp.delegate as? AppDelegate)?.selfUninstall() + } + + @objc func selfTerminate(_ sender: Any?) { + NSApp.terminate(nil) + } + + @objc func checkForUpdate(_ sender: Any?) { + (NSApp.delegate as? AppDelegate)?.checkForUpdate(forced: true) + } + + private func open(userFileAt path: String) { + func checkIfUserFilesExist() -> Bool { + if !mgrLangModel.checkIfUserLanguageModelFilesExist() { + let content = String( + format: NSLocalizedString( + "Please check the permission at \"%@\".", comment: ""), + mgrLangModel.dataFolderPath(isDefaultFolder: false)) + ctlNonModalAlertWindow.shared.show( + title: NSLocalizedString("Unable to create the user phrase file.", comment: ""), + content: content, confirmButtonTitle: NSLocalizedString("OK", comment: ""), + cancelButtonTitle: nil, cancelAsDefault: false, delegate: nil) + NSApp.setActivationPolicy(.accessory) + return false + } + return true + } + + if !checkIfUserFilesExist() { + return + } + NSWorkspace.shared.openFile(path, withApplication: "vChewingPhraseEditor") + } + + @objc func openUserPhrases(_ sender: Any?) { + open(userFileAt: mgrLangModel.userPhrasesDataPath(keyHandler.inputMode)) + } + + @objc func openUserDataFolder(_ sender: Any?) { + if !mgrLangModel.checkIfUserDataFolderExists() { + return + } + NSWorkspace.shared.openFile( + mgrLangModel.dataFolderPath(isDefaultFolder: false), withApplication: "Finder") + } + + @objc func openExcludedPhrases(_ sender: Any?) { + open(userFileAt: mgrLangModel.excludedPhrasesDataPath(keyHandler.inputMode)) + } + + @objc func openUserSymbols(_ sender: Any?) { + open(userFileAt: mgrLangModel.userSymbolDataPath(keyHandler.inputMode)) + } + + @objc func openPhraseReplacement(_ sender: Any?) { + open(userFileAt: mgrLangModel.phraseReplacementDataPath(keyHandler.inputMode)) + } + + @objc func openAssociatedPhrases(_ sender: Any?) { + open(userFileAt: mgrLangModel.userAssociatedPhrasesDataPath(keyHandler.inputMode)) + } + + @objc func reloadUserPhrases(_ sender: Any?) { + mgrLangModel.loadUserPhrases() + mgrLangModel.loadUserPhraseReplacement() + } + + @objc func showAbout(_ sender: Any?) { + (NSApp.delegate as? AppDelegate)?.showAbout() + NSApp.activate(ignoringOtherApps: true) + } } @@ -346,391 +446,454 @@ class ctlInputMethod: IMKInputController { extension ctlInputMethod { - private func handle(state newState: InputState, client: Any?) { - let previous = state - state = newState + 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) - } else if let newState = newState as? InputState.AssociatedPhrases { - handle(state: newState, previous: previous, client: client) - } - } + 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) + } else if let newState = newState as? InputState.AssociatedPhrases { + handle(state: newState, previous: previous, client: client) + } + } - private func commit(text: String, client: Any!) { + private func commit(text: String, client: Any!) { - func kanjiConversionIfRequired(_ text: String) -> String { - if keyHandler.inputMode == InputMode.imeModeCHT { - if !mgrPrefs.chineseConversionEnabled && mgrPrefs.shiftJISShinjitaiOutputEnabled { - return vChewingKanjiConverter.cnvTradToJIS(text) - } - if mgrPrefs.chineseConversionEnabled && !mgrPrefs.shiftJISShinjitaiOutputEnabled { - return vChewingKanjiConverter.cnvTradToKangXi(text) - } - // 本來這兩個開關不該同時開啟的,但萬一被開啟了的話就這樣處理: - if mgrPrefs.chineseConversionEnabled && mgrPrefs.shiftJISShinjitaiOutputEnabled { - return vChewingKanjiConverter.cnvTradToJIS(text) - } - // if (!mgrPrefs.chineseConversionEnabled && !mgrPrefs.shiftJISShinjitaiOutputEnabled) || (keyHandler.inputMode != InputMode.imeModeCHT); - return text - } - return text - } + func kanjiConversionIfRequired(_ text: String) -> String { + if keyHandler.inputMode == InputMode.imeModeCHT { + if !mgrPrefs.chineseConversionEnabled && mgrPrefs.shiftJISShinjitaiOutputEnabled { + return vChewingKanjiConverter.cnvTradToJIS(text) + } + if mgrPrefs.chineseConversionEnabled && !mgrPrefs.shiftJISShinjitaiOutputEnabled { + return vChewingKanjiConverter.cnvTradToKangXi(text) + } + // 本來這兩個開關不該同時開啟的,但萬一被開啟了的話就這樣處理: + if mgrPrefs.chineseConversionEnabled && mgrPrefs.shiftJISShinjitaiOutputEnabled { + return vChewingKanjiConverter.cnvTradToJIS(text) + } + // if (!mgrPrefs.chineseConversionEnabled && !mgrPrefs.shiftJISShinjitaiOutputEnabled) || (keyHandler.inputMode != InputMode.imeModeCHT); + return text + } + return text + } - let buffer = kanjiConversionIfRequired(text) - if buffer.isEmpty { - return - } - (client as? IMKTextInput)?.insertText(buffer, replacementRange: NSRange(location: NSNotFound, length: NSNotFound)) - } + let buffer = kanjiConversionIfRequired(text) + if buffer.isEmpty { + return + } + (client as? IMKTextInput)?.insertText( + buffer, replacementRange: NSRange(location: NSNotFound, length: NSNotFound)) + } - private func handle(state: InputState.Deactivated, previous: InputState, client: Any?) { - currentCandidateClient = nil + private func handle(state: InputState.Deactivated, previous: InputState, client: Any?) { + currentCandidateClient = nil - gCurrentCandidateController?.delegate = nil - gCurrentCandidateController?.visible = false - hideTooltip() + 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)) - } + 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() + private func handle(state: InputState.Empty, previous: InputState, client: Any?) { + gCurrentCandidateController?.visible = false + hideTooltip() - guard let client = client as? IMKTextInput else { - return - } + 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)) - } + 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() + private func handle( + state: InputState.EmptyIgnoringPreviousState, previous: InputState, client: Any! + ) { + gCurrentCandidateController?.visible = false + hideTooltip() - guard let client = client as? IMKTextInput else { - return - } + guard let client = client as? IMKTextInput else { + return + } - client.setMarkedText("", selectionRange: NSMakeRange(0, 0), replacementRange: NSMakeRange(NSNotFound, NSNotFound)) - } + client.setMarkedText( + "", selectionRange: NSMakeRange(0, 0), + replacementRange: NSMakeRange(NSNotFound, NSNotFound)) + } - private func handle(state: InputState.Committing, previous: InputState, client: Any?) { - gCurrentCandidateController?.visible = false - hideTooltip() + private func handle(state: InputState.Committing, previous: InputState, client: Any?) { + gCurrentCandidateController?.visible = false + hideTooltip() - guard let client = client as? IMKTextInput else { - return - } + 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)) - } + 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() + private func handle(state: InputState.Inputting, previous: InputState, client: Any?) { + gCurrentCandidateController?.visible = false + hideTooltip() - guard let client = client as? IMKTextInput else { - return - } + guard let client = client as? IMKTextInput else { + return + } - let poppedText = state.poppedText - if !poppedText.isEmpty { - commit(text: poppedText, client: client) - } + 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)) - if !state.tooltip.isEmpty { - show(tooltip: state.tooltip, composingBuffer: state.composingBuffer, cursorIndex: state.cursorIndex, 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)) + if !state.tooltip.isEmpty { + show( + tooltip: state.tooltip, composingBuffer: state.composingBuffer, + cursorIndex: state.cursorIndex, client: client) + } + } - private func handle(state: InputState.Marking, previous: InputState, client: Any?) { - gCurrentCandidateController?.visible = false - guard let client = client as? IMKTextInput else { - hideTooltip() - return - } + 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)) + // 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.markerIndex, client: client) - } - } + if state.tooltip.isEmpty { + hideTooltip() + } else { + show( + tooltip: state.tooltip, composingBuffer: state.composingBuffer, + cursorIndex: state.markerIndex, client: client) + } + } - private func handle(state: InputState.ChoosingCandidate, previous: InputState, client: Any?) { - hideTooltip() - guard let client = client as? IMKTextInput else { - gCurrentCandidateController?.visible = false - return - } + 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)) - show(candidateWindowWith: state, 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)) + show(candidateWindowWith: state, client: client) + } - private func handle(state: InputState.AssociatedPhrases, previous: InputState, client: Any?) { - hideTooltip() - guard let client = client as? IMKTextInput else { - gCurrentCandidateController?.visible = false - return - } - client.setMarkedText("", selectionRange: NSMakeRange(0, 0), replacementRange: NSMakeRange(NSNotFound, NSNotFound)) - show(candidateWindowWith: state, client: client) - } + private func handle(state: InputState.AssociatedPhrases, previous: InputState, client: Any?) { + hideTooltip() + guard let client = client as? IMKTextInput else { + gCurrentCandidateController?.visible = false + return + } + client.setMarkedText( + "", selectionRange: NSMakeRange(0, 0), + replacementRange: NSMakeRange(NSNotFound, NSNotFound)) + show(candidateWindowWith: state, client: client) + } } // MARK: - extension ctlInputMethod { - private func show(candidateWindowWith state: InputState, client: Any!) { - let useVerticalMode: Bool = { - var useVerticalMode = false - var candidates: [String] = [] - if let state = state as? InputState.ChoosingCandidate { - useVerticalMode = state.useVerticalMode - candidates = state.candidates - } else if let state = state as? InputState.AssociatedPhrases { - useVerticalMode = state.useVerticalMode - candidates = state.candidates - } - if useVerticalMode == true { - return true - } - candidates.sort { - return $0.count > $1.count - } - // If there is a candidate which is too long, we use the vertical - // candidate list window automatically. - if candidates.first?.count ?? 0 > 8 { - // return true // 禁用這一項。威注音回頭會換候選窗格。 - } - return false - }() - - gCurrentCandidateController?.delegate = nil + private func show(candidateWindowWith state: InputState, client: Any!) { + let useVerticalMode: Bool = { + var useVerticalMode = false + var candidates: [String] = [] + if let state = state as? InputState.ChoosingCandidate { + useVerticalMode = state.useVerticalMode + candidates = state.candidates + } else if let state = state as? InputState.AssociatedPhrases { + useVerticalMode = state.useVerticalMode + candidates = state.candidates + } + if useVerticalMode == true { + return true + } + candidates.sort { + return $0.count > $1.count + } + // If there is a candidate which is too long, we use the vertical + // candidate list window automatically. + if candidates.first?.count ?? 0 > 8 { + // return true // 禁用這一項。威注音回頭會換候選窗格。 + } + return false + }() - if useVerticalMode { - gCurrentCandidateController = .vertical - } else if mgrPrefs.useHorizontalCandidateList { - gCurrentCandidateController = .horizontal - } else { - gCurrentCandidateController = .vertical - } + gCurrentCandidateController?.delegate = nil - // set the attributes for the candidate panel (which uses NSAttributedString) - let textSize = mgrPrefs.candidateListTextSize - let keyLabelSize = max(textSize / 2, kMinKeyLabelSize) + if useVerticalMode { + gCurrentCandidateController = .vertical + } else if mgrPrefs.useHorizontalCandidateList { + gCurrentCandidateController = .horizontal + } else { + gCurrentCandidateController = .vertical + } - func labelFont(name: String?, size: CGFloat) -> NSFont { - if let name = name { - return NSFont(name: name, size: size) ?? NSFont.systemFont(ofSize: size) - } - return NSFont.systemFont(ofSize: size) - } + // set the attributes for the candidate panel (which uses NSAttributedString) + let textSize = mgrPrefs.candidateListTextSize + let keyLabelSize = max(textSize / 2, kMinKeyLabelSize) - func candidateFont(name: String?, size: CGFloat) -> NSFont { - let currentMUIFont = (keyHandler.inputMode == InputMode.imeModeCHS) ? "Sarasa Term Slab SC" : "Sarasa Term Slab TC" - var finalReturnFont = NSFont(name: currentMUIFont, size: size) ?? NSFont.systemFont(ofSize: size) - // 對更紗黑體的依賴到 macOS 11 Big Sur 為止。macOS 12 Monterey 開始則依賴系統內建的函數使用蘋方來處理。 - if #available(macOS 12.0, *) {finalReturnFont = NSFont.systemFont(ofSize: size)} - if let name = name { - return NSFont(name: name, size: size) ?? finalReturnFont - } - return finalReturnFont - } + func labelFont(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 = labelFont(name: mgrPrefs.candidateKeyLabelFontName, size: keyLabelSize) - gCurrentCandidateController?.candidateFont = candidateFont(name: mgrPrefs.candidateTextFontName, size: textSize) + func candidateFont(name: String?, size: CGFloat) -> NSFont { + let currentMUIFont = + (keyHandler.inputMode == InputMode.imeModeCHS) + ? "Sarasa Term Slab SC" : "Sarasa Term Slab TC" + var finalReturnFont = + NSFont(name: currentMUIFont, size: size) ?? NSFont.systemFont(ofSize: size) + // 對更紗黑體的依賴到 macOS 11 Big Sur 為止。macOS 12 Monterey 開始則依賴系統內建的函數使用蘋方來處理。 + if #available(macOS 12.0, *) { finalReturnFont = NSFont.systemFont(ofSize: size) } + if let name = name { + return NSFont(name: name, size: size) ?? finalReturnFont + } + return finalReturnFont + } - let candidateKeys = mgrPrefs.candidateKeys - let keyLabels = candidateKeys.count > 4 ? Array(candidateKeys) : Array(mgrPrefs.defaultCandidateKeys) - let keyLabelSuffix = state is InputState.AssociatedPhrases ? "^" : "" - gCurrentCandidateController?.keyLabels = keyLabels.map { - CandidateKeyLabel(key: String($0), displayedText: String($0) + keyLabelSuffix) - } + gCurrentCandidateController?.keyLabelFont = labelFont( + name: mgrPrefs.candidateKeyLabelFontName, size: keyLabelSize) + gCurrentCandidateController?.candidateFont = candidateFont( + name: mgrPrefs.candidateTextFontName, size: textSize) - gCurrentCandidateController?.delegate = self - gCurrentCandidateController?.reloadData() - currentCandidateClient = client + let candidateKeys = mgrPrefs.candidateKeys + let keyLabels = + candidateKeys.count > 4 ? Array(candidateKeys) : Array(mgrPrefs.defaultCandidateKeys) + let keyLabelSuffix = state is InputState.AssociatedPhrases ? "^" : "" + gCurrentCandidateController?.keyLabels = keyLabels.map { + CandidateKeyLabel(key: String($0), displayedText: String($0) + keyLabelSuffix) + } - gCurrentCandidateController?.visible = true + gCurrentCandidateController?.delegate = self + gCurrentCandidateController?.reloadData() + currentCandidateClient = client - var lineHeightRect = NSMakeRect(0.0, 0.0, 16.0, 16.0) - var cursor: Int = 0 + gCurrentCandidateController?.visible = true - if let state = state as? InputState.ChoosingCandidate { - cursor = Int(state.cursorIndex) - if cursor == state.composingBuffer.count && cursor != 0 { - cursor -= 1 - } - } + var lineHeightRect = NSMakeRect(0.0, 0.0, 16.0, 16.0) + var cursor: Int = 0 - while lineHeightRect.origin.x == 0 && lineHeightRect.origin.y == 0 && cursor >= 0 { - (client as? IMKTextInput)?.attributes(forCharacterIndex: cursor, lineHeightRectangle: &lineHeightRect) - cursor -= 1 - } + if let state = state as? InputState.ChoosingCandidate { + cursor = Int(state.cursorIndex) + if cursor == state.composingBuffer.count && cursor != 0 { + cursor -= 1 + } + } - if 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) - } - } + while lineHeightRect.origin.x == 0 && lineHeightRect.origin.y == 0 && cursor >= 0 { + (client as? IMKTextInput)?.attributes( + forCharacterIndex: cursor, lineHeightRectangle: &lineHeightRect) + cursor -= 1 + } - private func show(tooltip: String, composingBuffer: String, cursorIndex: UInt, client: Any!) { - var lineHeightRect = NSMakeRect(0.0, 0.0, 16.0, 16.0) - var cursor: Int = Int(cursorIndex) - if cursor == composingBuffer.count && cursor != 0 { - cursor -= 1 - } - while lineHeightRect.origin.x == 0 && lineHeightRect.origin.y == 0 && cursor >= 0 { - (client as? IMKTextInput)?.attributes(forCharacterIndex: cursor, lineHeightRectangle: &lineHeightRect) - cursor -= 1 - } - ctlInputMethod.tooltipController.show(tooltip: tooltip, at: lineHeightRect.origin) - } + if 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 hideTooltip() { - ctlInputMethod.tooltipController.hide() - } + private func show(tooltip: String, composingBuffer: String, cursorIndex: UInt, client: Any!) { + var lineHeightRect = NSMakeRect(0.0, 0.0, 16.0, 16.0) + var cursor: Int = Int(cursorIndex) + if cursor == composingBuffer.count && cursor != 0 { + cursor -= 1 + } + while lineHeightRect.origin.x == 0 && lineHeightRect.origin.y == 0 && cursor >= 0 { + (client as? IMKTextInput)?.attributes( + forCharacterIndex: cursor, lineHeightRectangle: &lineHeightRect) + cursor -= 1 + } + ctlInputMethod.tooltipController.show(tooltip: tooltip, at: lineHeightRect.origin) + } + + private func hideTooltip() { + ctlInputMethod.tooltipController.hide() + } } // MARK: - 開關判定當前應用究竟是? @objc extension ctlInputMethod { - @objc static var areWeUsingOurOwnPhraseEditor: Bool = false + @objc static var areWeUsingOurOwnPhraseEditor: Bool = false } // MARK: - extension ctlInputMethod: KeyHandlerDelegate { - func candidateController(for keyHandler: KeyHandler) -> Any { - gCurrentCandidateController ?? .vertical - } + func candidateController(for keyHandler: KeyHandler) -> Any { + gCurrentCandidateController ?? .vertical + } - 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, 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 - } - let InputModeReversed: InputMode = (keyHandler.inputMode == InputMode.imeModeCHT) ? InputMode.imeModeCHS : InputMode.imeModeCHT - mgrLangModel.writeUserPhrase(state.userPhrase, inputMode: keyHandler.inputMode, areWeDuplicating: state.chkIfUserPhraseExists, areWeDeleting: ctlInputMethod.areWeDeleting) - mgrLangModel.writeUserPhrase(state.userPhraseConverted, inputMode: InputModeReversed, areWeDuplicating: false, areWeDeleting: ctlInputMethod.areWeDeleting) - return true - } + func keyHandler(_ keyHandler: KeyHandler, didRequestWriteUserPhraseWith state: InputState) + -> Bool + { + guard let state = state as? InputState.Marking else { + return false + } + if !state.validToWrite { + return false + } + let refInputModeReversed: InputMode = + (keyHandler.inputMode == InputMode.imeModeCHT) + ? InputMode.imeModeCHS : InputMode.imeModeCHT + mgrLangModel.writeUserPhrase( + state.userPhrase, inputMode: keyHandler.inputMode, + areWeDuplicating: state.chkIfUserPhraseExists, + areWeDeleting: ctlInputMethod.areWeDeleting) + mgrLangModel.writeUserPhrase( + state.userPhraseConverted, inputMode: refInputModeReversed, + areWeDuplicating: false, + areWeDeleting: ctlInputMethod.areWeDeleting) + return true + } } // MARK: - extension ctlInputMethod: CandidateControllerDelegate { - func candidateCountForController(_ controller: CandidateController) -> UInt { - if let state = state as? InputState.ChoosingCandidate { - return UInt(state.candidates.count) - } else if let state = state as? InputState.AssociatedPhrases { - return UInt(state.candidates.count) - } - return 0 - } + func candidateCountForController(_ controller: CandidateController) -> UInt { + if let state = state as? InputState.ChoosingCandidate { + return UInt(state.candidates.count) + } else if let state = state as? InputState.AssociatedPhrases { + 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)] - } else if let state = state as? InputState.AssociatedPhrases { - return state.candidates[Int(index)] - } - return "" - } + func candidateController(_ controller: CandidateController, candidateAtIndex index: UInt) + -> String + { + if let state = state as? InputState.ChoosingCandidate { + return state.candidates[Int(index)] + } else if let state = state as? InputState.AssociatedPhrases { + return state.candidates[Int(index)] + } + return "" + } - func candidateController(_ controller: CandidateController, didSelectCandidateAtIndex index: UInt) { - let client = currentCandidateClient + func candidateController( + _ controller: CandidateController, didSelectCandidateAtIndex index: UInt + ) { + let client = currentCandidateClient - if let state = state as? InputState.SymbolTable, - let node = state.node.children?[Int(index)] { - if let children = node.children, !children.isEmpty { - self.handle(state: .SymbolTable(node: node, useVerticalMode: state.useVerticalMode), client: currentCandidateClient) - } else { - self.handle(state: .Committing(poppedText: node.title), client: client) - self.handle(state: .Empty(), client: client) - } - return - } + if let state = state as? InputState.SymbolTable, + let node = state.node.children?[Int(index)] + { + if let children = node.children, !children.isEmpty { + self.handle( + state: .SymbolTable(node: node, useVerticalMode: state.useVerticalMode), + client: currentCandidateClient) + } else { + self.handle(state: .Committing(poppedText: node.title), client: client) + self.handle(state: .Empty(), client: client) + } + return + } - if let state = state as? InputState.ChoosingCandidate { - let selectedValue = state.candidates[Int(index)] - keyHandler.fixNode(value: selectedValue) + if let state = state as? InputState.ChoosingCandidate { + let selectedValue = state.candidates[Int(index)] + keyHandler.fixNode(value: selectedValue) - guard let inputting = keyHandler.buildInputtingState() as? InputState.Inputting else { - return - } + guard let inputting = keyHandler.buildInputtingState() as? InputState.Inputting else { + return + } - if mgrPrefs.useSCPCTypingMode { - keyHandler.clear() - let composingBuffer = inputting.composingBuffer - handle(state: .Committing(poppedText: composingBuffer), client: client) - if mgrPrefs.associatedPhrasesEnabled, - let associatePhrases = keyHandler.buildAssociatePhraseState(withKey: composingBuffer, useVerticalMode: state.useVerticalMode) as? InputState.AssociatedPhrases { - self.handle(state: associatePhrases, client: client) - } else { - handle(state: .Empty(), client: client) - } - } else { - handle(state: inputting, client: client) - } - return - } + if mgrPrefs.useSCPCTypingMode { + keyHandler.clear() + let composingBuffer = inputting.composingBuffer + handle(state: .Committing(poppedText: composingBuffer), client: client) + if mgrPrefs.associatedPhrasesEnabled, + let associatePhrases = keyHandler.buildAssociatePhraseState( + withKey: composingBuffer, useVerticalMode: state.useVerticalMode) + as? InputState.AssociatedPhrases + { + self.handle(state: associatePhrases, client: client) + } else { + handle(state: .Empty(), client: client) + } + } else { + handle(state: inputting, client: client) + } + return + } - if let state = state as? InputState.AssociatedPhrases { - let selectedValue = state.candidates[Int(index)] - handle(state: .Committing(poppedText: selectedValue), client: currentCandidateClient) - if mgrPrefs.associatedPhrasesEnabled, - let associatePhrases = keyHandler.buildAssociatePhraseState(withKey: selectedValue, useVerticalMode: state.useVerticalMode) as? InputState.AssociatedPhrases { - self.handle(state: associatePhrases, client: client) - } else { - handle(state: .Empty(), client: client) - } - } - } + if let state = state as? InputState.AssociatedPhrases { + let selectedValue = state.candidates[Int(index)] + handle(state: .Committing(poppedText: selectedValue), client: currentCandidateClient) + if mgrPrefs.associatedPhrasesEnabled, + let associatePhrases = keyHandler.buildAssociatePhraseState( + withKey: selectedValue, useVerticalMode: state.useVerticalMode) + as? InputState.AssociatedPhrases + { + self.handle(state: associatePhrases, client: client) + } else { + handle(state: .Empty(), client: client) + } + } + } } - diff --git a/Source/Modules/IMEModules/mgrPrefs.swift b/Source/Modules/IMEModules/mgrPrefs.swift index 96b817bf..1ab95969 100644 --- a/Source/Modules/IMEModules/mgrPrefs.swift +++ b/Source/Modules/IMEModules/mgrPrefs.swift @@ -1,20 +1,27 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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 @@ -72,464 +79,492 @@ private let kDefaultKeys = "123456789" @propertyWrapper struct UserDefault { - let key: String - let defaultValue: Value - var container: UserDefaults = .standard + let key: String + let defaultValue: Value + var container: UserDefaults = .standard - var wrappedValue: Value { - get { - container.object(forKey: key) as? Value ?? defaultValue - } - set { - container.set(newValue, forKey: key) - } - } + var wrappedValue: Value { + get { + container.object(forKey: key) as? Value ?? defaultValue + } + set { + container.set(newValue, forKey: key) + } + } } @propertyWrapper struct CandidateListTextSize { - let key: String - let defaultValue: CGFloat = kDefaultCandidateListTextSize - lazy var container: UserDefault = { - UserDefault(key: key, defaultValue: defaultValue) - }() + let key: String + let defaultValue: CGFloat = kDefaultCandidateListTextSize + lazy var container: UserDefault = { + UserDefault(key: key, defaultValue: defaultValue) + }() - var wrappedValue: CGFloat { - mutating get { - var value = container.wrappedValue - if value < kMinCandidateListTextSize { - value = kMinCandidateListTextSize - } else if value > kMaxCandidateListTextSize { - value = kMaxCandidateListTextSize - } - return value - } - set { - var value = newValue - if value < kMinCandidateListTextSize { - value = kMinCandidateListTextSize - } else if value > kMaxCandidateListTextSize { - value = kMaxCandidateListTextSize - } - container.wrappedValue = value - } - } + var wrappedValue: CGFloat { + mutating get { + var value = container.wrappedValue + if value < kMinCandidateListTextSize { + value = kMinCandidateListTextSize + } else if value > kMaxCandidateListTextSize { + value = kMaxCandidateListTextSize + } + return value + } + set { + var value = newValue + if value < kMinCandidateListTextSize { + value = kMinCandidateListTextSize + } else if value > kMaxCandidateListTextSize { + value = kMaxCandidateListTextSize + } + container.wrappedValue = value + } + } } @propertyWrapper struct ComposingBufferSize { - let key: String - let defaultValue: Int = kDefaultComposingBufferSize - lazy var container: UserDefault = { - UserDefault(key: key, defaultValue: defaultValue) - }() + let key: String + let defaultValue: Int = kDefaultComposingBufferSize + lazy var container: UserDefault = { + UserDefault(key: key, defaultValue: defaultValue) + }() - var wrappedValue: Int { - mutating get { - let currentValue = container.wrappedValue - if currentValue < kMinComposingBufferSize { - return kMinComposingBufferSize - } else if currentValue > kMaxComposingBufferSize { - return kMaxComposingBufferSize - } - return currentValue - } - set { - var value = newValue - if value < kMinComposingBufferSize { - value = kMinComposingBufferSize - } else if value > kMaxComposingBufferSize { - value = kMaxComposingBufferSize - } - container.wrappedValue = value - } - } + var wrappedValue: Int { + mutating get { + let currentValue = container.wrappedValue + if currentValue < kMinComposingBufferSize { + return kMinComposingBufferSize + } else if currentValue > kMaxComposingBufferSize { + return kMaxComposingBufferSize + } + return currentValue + } + set { + var value = newValue + if value < kMinComposingBufferSize { + value = kMinComposingBufferSize + } else if value > kMaxComposingBufferSize { + value = kMaxComposingBufferSize + } + container.wrappedValue = value + } + } } // MARK: - @objc enum KeyboardLayout: Int { - case standard = 0 - case eten = 1 - case hsu = 2 - case eten26 = 3 - case IBM = 4 - case MiTAC = 5 - case FakeSeigyou = 6 - case hanyuPinyin = 10 + case standard = 0 + case eten = 1 + case hsu = 2 + case eten26 = 3 + case ibm = 4 + case mitac = 5 + case fakeSeigyou = 6 + case hanyuPinyin = 10 - var name: String { - switch (self) { - case .standard: - return "Standard" - case .eten: - return "ETen" - case .hsu: - return "Hsu" - case .eten26: - return "ETen26" - case .IBM: - return "IBM" - case .MiTAC: - return "MiTAC" - case .FakeSeigyou: - return "FakeSeigyou" - case .hanyuPinyin: - return "HanyuPinyin" - } - } + var name: String { + switch self { + case .standard: + return "Standard" + case .eten: + return "ETen" + case .hsu: + return "Hsu" + case .eten26: + return "ETen26" + case .ibm: + return "IBM" + case .mitac: + return "MiTAC" + case .fakeSeigyou: + return "FakeSeigyou" + case .hanyuPinyin: + return "HanyuPinyin" + } + } } // MARK: - @objc public class mgrPrefs: NSObject { - static var allKeys:[String] { - [kIsDebugModeEnabled, - kUserDataFolderSpecified, - kKeyboardLayoutPreference, - kBasisKeyboardLayoutPreference, - kShowPageButtonsInCandidateWindow, - kCandidateListTextSize, - kAppleLanguagesPreferences, - kShouldAutoReloadUserDataFiles, - kSelectPhraseAfterCursorAsCandidatePreference, - kUseHorizontalCandidateListPreference, - kComposingBufferSizePreference, - kChooseCandidateUsingSpace, - kCNS11643Enabled, - kSymbolInputEnabled, - kChineseConversionEnabled, - kShiftJISShinjitaiOutputEnabled, - kHalfWidthPunctuationEnabled, - kSpecifyTabKeyBehavior, - kSpecifySpaceKeyBehavior, - kEscToCleanInputBuffer, - kCandidateTextFontName, - kCandidateKeyLabelFontName, - kCandidateKeys, - kMoveCursorAfterSelectingCandidate, - kPhraseReplacementEnabled, - kUseSCPCTypingMode, - kMaxCandidateLength, - kShouldNotFartInLieuOfBeep, - kAssociatedPhrasesEnabled] - } - - @objc public static func setMissingDefaults () { - // 既然 Preferences Module 的預設屬性不自動寫入 plist、而且還是 private,那這邊就先寫入了。 - - // 首次啟用輸入法時不要啟用偵錯模式。 - if UserDefaults.standard.object(forKey: kIsDebugModeEnabled) == nil { - UserDefaults.standard.set(mgrPrefs.isDebugModeEnabled, forKey: kIsDebugModeEnabled) - } - - // 首次啟用輸入法時設定不要自動更新,免得在某些要隔絕外部網路連線的保密機構內觸犯資安規則。 - if UserDefaults.standard.object(forKey: kCheckUpdateAutomatically) == nil { - UserDefaults.standard.set(false, forKey: kCheckUpdateAutomatically) - } - - // 預設顯示選字窗翻頁按鈕 - if UserDefaults.standard.object(forKey: kShowPageButtonsInCandidateWindow) == nil { - UserDefaults.standard.set(mgrPrefs.showPageButtonsInCandidateWindow, forKey: kShowPageButtonsInCandidateWindow) - } - - // 預設啟用繪文字與符號輸入 - if UserDefaults.standard.object(forKey: kSymbolInputEnabled) == nil { - UserDefaults.standard.set(mgrPrefs.symbolInputEnabled, forKey: kSymbolInputEnabled) - } - - // 預設選字窗字詞文字尺寸,設成 18 剛剛好 - if UserDefaults.standard.object(forKey: kCandidateListTextSize) == nil { - UserDefaults.standard.set(mgrPrefs.candidateListTextSize, forKey: kCandidateListTextSize) - } - - // 預設摁空格鍵來選字,所以設成 true - if UserDefaults.standard.object(forKey: kChooseCandidateUsingSpace) == nil { - UserDefaults.standard.set(mgrPrefs.chooseCandidateUsingSpace, forKey: kChooseCandidateUsingSpace) - } - - // 自動檢測使用者自訂語彙數據的變動並載入。 - if UserDefaults.standard.object(forKey: kShouldAutoReloadUserDataFiles) == nil { - UserDefaults.standard.set(mgrPrefs.shouldAutoReloadUserDataFiles, forKey: kShouldAutoReloadUserDataFiles) - } - - // 預設情況下讓 Tab 鍵在選字窗內切換候選字、而不是用來換頁。 - if UserDefaults.standard.object(forKey: kSpecifyTabKeyBehavior) == nil { - UserDefaults.standard.set(mgrPrefs.specifyTabKeyBehavior, forKey: kSpecifyTabKeyBehavior) - } - - // 預設情況下讓 Space 鍵在選字窗內切換候選字、而不是用來換頁。 - if UserDefaults.standard.object(forKey: kSpecifySpaceKeyBehavior) == nil { - UserDefaults.standard.set(mgrPrefs.specifySpaceKeyBehavior, forKey: kSpecifySpaceKeyBehavior) - } - - // 預設禁用逐字選字模式(就是每個字都要選的那種),所以設成 false - if UserDefaults.standard.object(forKey: kUseSCPCTypingMode) == nil { - UserDefaults.standard.set(mgrPrefs.useSCPCTypingMode, forKey: kUseSCPCTypingMode) - } - - // 預設禁用逐字選字模式時的聯想詞功能,所以設成 false - if UserDefaults.standard.object(forKey: kAssociatedPhrasesEnabled) == nil { - UserDefaults.standard.set(mgrPrefs.associatedPhrasesEnabled, forKey: kAssociatedPhrasesEnabled) - } - - // 預設漢音風格選字,所以要設成 0 - if UserDefaults.standard.object(forKey: kSelectPhraseAfterCursorAsCandidatePreference) == nil { - UserDefaults.standard.set(mgrPrefs.selectPhraseAfterCursorAsCandidate, forKey: kSelectPhraseAfterCursorAsCandidatePreference) - } - - // 預設在選字後自動移動游標 - if UserDefaults.standard.object(forKey: kMoveCursorAfterSelectingCandidate) == nil { - UserDefaults.standard.set(mgrPrefs.moveCursorAfterSelectingCandidate, forKey: kMoveCursorAfterSelectingCandidate) - } - - // 預設橫向選字窗,不爽請自行改成縱向選字窗 - if UserDefaults.standard.object(forKey: kUseHorizontalCandidateListPreference) == nil { - UserDefaults.standard.set(mgrPrefs.useHorizontalCandidateList, forKey: kUseHorizontalCandidateListPreference) - } - - // 預設停用全字庫支援 - if UserDefaults.standard.object(forKey: kCNS11643Enabled) == nil { - UserDefaults.standard.set(mgrPrefs.cns11643Enabled, forKey: kCNS11643Enabled) - } - - // 預設停用繁體轉康熙模組 - if UserDefaults.standard.object(forKey: kChineseConversionEnabled) == nil { - UserDefaults.standard.set(mgrPrefs.chineseConversionEnabled, forKey: kChineseConversionEnabled) - } - - // 預設停用繁體轉 JIS 當用新字體模組 - if UserDefaults.standard.object(forKey: kShiftJISShinjitaiOutputEnabled) == nil { - UserDefaults.standard.set(mgrPrefs.shiftJISShinjitaiOutputEnabled, forKey: kShiftJISShinjitaiOutputEnabled) - } - - // 預設停用自訂語彙置換 - if UserDefaults.standard.object(forKey: kPhraseReplacementEnabled) == nil { - UserDefaults.standard.set(mgrPrefs.phraseReplacementEnabled, forKey: kPhraseReplacementEnabled) - } - - // 預設沒事不要在那裡放屁 - if UserDefaults.standard.object(forKey: kShouldNotFartInLieuOfBeep) == nil { - UserDefaults.standard.set(mgrPrefs.shouldNotFartInLieuOfBeep, forKey: kShouldNotFartInLieuOfBeep) - } - - UserDefaults.standard.synchronize() - } - - @UserDefault(key: kIsDebugModeEnabled, defaultValue: false) - @objc static var isDebugModeEnabled: Bool - - @UserDefault(key: kUserDataFolderSpecified, defaultValue: "") - @objc static var userDataFolderSpecified: String - - @objc static func ifSpecifiedUserDataPathExistsInPlist() -> Bool { - UserDefaults.standard.object(forKey: kUserDataFolderSpecified) != nil - } - - @UserDefault(key: kAppleLanguagesPreferences, defaultValue: []) - @objc static var appleLanguages: Array - - @UserDefault(key: kKeyboardLayoutPreference, defaultValue: 0) - @objc static var keyboardLayout: Int - - @objc static var keyboardLayoutName: String { - (KeyboardLayout(rawValue: self.keyboardLayout) ?? KeyboardLayout.standard).name - } - - @UserDefault(key: kBasisKeyboardLayoutPreference, defaultValue: "com.apple.keylayout.ZhuyinBopomofo") - @objc static var basisKeyboardLayout: String - - @UserDefault(key: kShowPageButtonsInCandidateWindow, defaultValue: true) - @objc static var showPageButtonsInCandidateWindow: Bool - - @CandidateListTextSize(key: kCandidateListTextSize) - @objc static var candidateListTextSize: CGFloat - - @UserDefault(key: kShouldAutoReloadUserDataFiles, defaultValue: true) - @objc static var shouldAutoReloadUserDataFiles: Bool - - @UserDefault(key: kSelectPhraseAfterCursorAsCandidatePreference, defaultValue: false) - @objc static var selectPhraseAfterCursorAsCandidate: Bool - - @UserDefault(key: kMoveCursorAfterSelectingCandidate, defaultValue: false) - @objc static var moveCursorAfterSelectingCandidate: Bool - - @UserDefault(key: kUseHorizontalCandidateListPreference, defaultValue: true) - @objc static var useHorizontalCandidateList: Bool - - @ComposingBufferSize(key: kComposingBufferSizePreference) - @objc static var composingBufferSize: Int - - @UserDefault(key: kChooseCandidateUsingSpace, defaultValue: true) - @objc static var chooseCandidateUsingSpace: Bool - - @UserDefault(key: kUseSCPCTypingMode, defaultValue: false) - @objc static var useSCPCTypingMode: Bool - - @objc static func toggleSCPCTypingModeEnabled() -> Bool { - useSCPCTypingMode = !useSCPCTypingMode - UserDefaults.standard.set(useSCPCTypingMode, forKey: kUseSCPCTypingMode) - return useSCPCTypingMode - } - - @UserDefault(key: kMaxCandidateLength, defaultValue: kDefaultComposingBufferSize * 2) - @objc static var maxCandidateLength: Int - - @UserDefault(key: kShouldNotFartInLieuOfBeep, defaultValue: true) - @objc static var shouldNotFartInLieuOfBeep: Bool - - @objc static func toggleShouldNotFartInLieuOfBeep() -> Bool { - shouldNotFartInLieuOfBeep = !shouldNotFartInLieuOfBeep - UserDefaults.standard.set(shouldNotFartInLieuOfBeep, forKey: kShouldNotFartInLieuOfBeep) - return shouldNotFartInLieuOfBeep - } - - @UserDefault(key: kCNS11643Enabled, defaultValue: false) - @objc static var cns11643Enabled: Bool - - @objc static func toggleCNS11643Enabled() -> Bool { - cns11643Enabled = !cns11643Enabled - mgrLangModel.setCNSEnabled(cns11643Enabled) // 很重要 - UserDefaults.standard.set(cns11643Enabled, forKey: kCNS11643Enabled) - return cns11643Enabled - } - - @UserDefault(key: kSymbolInputEnabled, defaultValue: true) - @objc static var symbolInputEnabled: Bool - - @objc static func toggleSymbolInputEnabled() -> Bool { - symbolInputEnabled = !symbolInputEnabled - mgrLangModel.setSymbolEnabled(symbolInputEnabled) // 很重要 - UserDefaults.standard.set(symbolInputEnabled, forKey: kSymbolInputEnabled) - return symbolInputEnabled - } - - @UserDefault(key: kChineseConversionEnabled, defaultValue: false) - @objc static var chineseConversionEnabled: Bool - - @objc @discardableResult static func toggleChineseConversionEnabled() -> Bool { - chineseConversionEnabled = !chineseConversionEnabled - // 康熙轉換與 JIS 轉換不能同時開啟,否則會出現某些奇奇怪怪的情況 - if chineseConversionEnabled && shiftJISShinjitaiOutputEnabled { - self.toggleShiftJISShinjitaiOutputEnabled() - UserDefaults.standard.set(shiftJISShinjitaiOutputEnabled, forKey: kShiftJISShinjitaiOutputEnabled) - } - UserDefaults.standard.set(chineseConversionEnabled, forKey: kChineseConversionEnabled) - return chineseConversionEnabled - } - - @UserDefault(key: kShiftJISShinjitaiOutputEnabled, defaultValue: false) - @objc static var shiftJISShinjitaiOutputEnabled: Bool - - @objc @discardableResult static func toggleShiftJISShinjitaiOutputEnabled() -> Bool { - shiftJISShinjitaiOutputEnabled = !shiftJISShinjitaiOutputEnabled - // 康熙轉換與 JIS 轉換不能同時開啟,否則會出現某些奇奇怪怪的情況 - if shiftJISShinjitaiOutputEnabled && chineseConversionEnabled {self.toggleChineseConversionEnabled()} - UserDefaults.standard.set(shiftJISShinjitaiOutputEnabled, forKey: kShiftJISShinjitaiOutputEnabled) - return shiftJISShinjitaiOutputEnabled - } - - @UserDefault(key: kHalfWidthPunctuationEnabled, defaultValue: false) - @objc static var halfWidthPunctuationEnabled: Bool - - @objc static func toggleHalfWidthPunctuationEnabled() -> Bool { - halfWidthPunctuationEnabled = !halfWidthPunctuationEnabled - return halfWidthPunctuationEnabled - } - - @UserDefault(key: kEscToCleanInputBuffer, defaultValue: true) - @objc static var escToCleanInputBuffer: Bool - - - @UserDefault(key: kSpecifyTabKeyBehavior, defaultValue: false) - @objc static var specifyTabKeyBehavior: Bool - - @UserDefault(key: kSpecifySpaceKeyBehavior, defaultValue: false) - @objc static var specifySpaceKeyBehavior: Bool - - // MARK: - Optional settings - @UserDefault(key: kCandidateTextFontName, defaultValue: nil) - @objc static var candidateTextFontName: String? - - @UserDefault(key: kCandidateKeyLabelFontName, defaultValue: nil) - @objc static var candidateKeyLabelFontName: String? - - @UserDefault(key: kCandidateKeys, defaultValue: kDefaultKeys) - @objc static var candidateKeys: String - - @objc static var defaultCandidateKeys: String { - kDefaultKeys - } - @objc static var suggestedCandidateKeys: [String] { - [kDefaultKeys, "234567890", "QWERTYUIO", "QWERTASDF", "ASDFGHJKL", "ASDFZXCVB"] - } - - @objc static func validate(candidateKeys: String) throws { - let trimmed = candidateKeys.trimmingCharacters(in: .whitespacesAndNewlines) - if trimmed.isEmpty { - throw CandidateKeyError.empty - } - if !trimmed.canBeConverted(to: .ascii) { - throw CandidateKeyError.invalidCharacters - } - if trimmed.contains(" ") { - throw CandidateKeyError.containSpace - } - if trimmed.count < 4 { - throw CandidateKeyError.tooShort - } - if trimmed.count > 15 { - throw CandidateKeyError.tooLong - } - let set = Set(Array(trimmed)) - if set.count != trimmed.count { - throw CandidateKeyError.duplicatedCharacters - } - } - - enum CandidateKeyError: Error, LocalizedError { - case empty - case invalidCharacters - case containSpace - case duplicatedCharacters - case tooShort - case tooLong - - var errorDescription: String? { - switch self { - case .empty: - return NSLocalizedString("Candidates keys cannot be empty.", comment: "") - case .invalidCharacters: - return NSLocalizedString("Candidate keys can only contain ASCII characters like alphanumericals.", comment: "") - case .containSpace: - return NSLocalizedString("Candidate keys cannot contain space.", comment: "") - case .duplicatedCharacters: - return NSLocalizedString("There should not be duplicated keys.", comment: "") - case .tooShort: - return NSLocalizedString("Please specify at least 4 candidate keys.", comment: "") - case .tooLong: - return NSLocalizedString("Maximum 15 candidate keys allowed.", comment: "") - } - } - - } - - @UserDefault(key: kPhraseReplacementEnabled, defaultValue: false) - @objc static var phraseReplacementEnabled: Bool - - @objc static func togglePhraseReplacementEnabled() -> Bool { - phraseReplacementEnabled = !phraseReplacementEnabled - mgrLangModel.setPhraseReplacementEnabled(phraseReplacementEnabled) - UserDefaults.standard.set(phraseReplacementEnabled, forKey: kPhraseReplacementEnabled) - return phraseReplacementEnabled - } - - @UserDefault(key: kAssociatedPhrasesEnabled, defaultValue: false) - @objc static var associatedPhrasesEnabled: Bool - - @objc static func toggleAssociatedPhrasesEnabled() -> Bool { - associatedPhrasesEnabled = !associatedPhrasesEnabled - UserDefaults.standard.set(associatedPhrasesEnabled, forKey: kAssociatedPhrasesEnabled) - return associatedPhrasesEnabled - } + static var allKeys: [String] { + [ + kIsDebugModeEnabled, + kUserDataFolderSpecified, + kKeyboardLayoutPreference, + kBasisKeyboardLayoutPreference, + kShowPageButtonsInCandidateWindow, + kCandidateListTextSize, + kAppleLanguagesPreferences, + kShouldAutoReloadUserDataFiles, + kSelectPhraseAfterCursorAsCandidatePreference, + kUseHorizontalCandidateListPreference, + kComposingBufferSizePreference, + kChooseCandidateUsingSpace, + kCNS11643Enabled, + kSymbolInputEnabled, + kChineseConversionEnabled, + kShiftJISShinjitaiOutputEnabled, + kHalfWidthPunctuationEnabled, + kSpecifyTabKeyBehavior, + kSpecifySpaceKeyBehavior, + kEscToCleanInputBuffer, + kCandidateTextFontName, + kCandidateKeyLabelFontName, + kCandidateKeys, + kMoveCursorAfterSelectingCandidate, + kPhraseReplacementEnabled, + kUseSCPCTypingMode, + kMaxCandidateLength, + kShouldNotFartInLieuOfBeep, + kAssociatedPhrasesEnabled, + ] + } + + @objc public static func setMissingDefaults() { + // 既然 Preferences Module 的預設屬性不自動寫入 plist、而且還是 private,那這邊就先寫入了。 + + // 首次啟用輸入法時不要啟用偵錯模式。 + if UserDefaults.standard.object(forKey: kIsDebugModeEnabled) == nil { + UserDefaults.standard.set(mgrPrefs.isDebugModeEnabled, forKey: kIsDebugModeEnabled) + } + + // 首次啟用輸入法時設定不要自動更新,免得在某些要隔絕外部網路連線的保密機構內觸犯資安規則。 + if UserDefaults.standard.object(forKey: kCheckUpdateAutomatically) == nil { + UserDefaults.standard.set(false, forKey: kCheckUpdateAutomatically) + } + + // 預設顯示選字窗翻頁按鈕 + if UserDefaults.standard.object(forKey: kShowPageButtonsInCandidateWindow) == nil { + UserDefaults.standard.set( + mgrPrefs.showPageButtonsInCandidateWindow, forKey: kShowPageButtonsInCandidateWindow + ) + } + + // 預設啟用繪文字與符號輸入 + if UserDefaults.standard.object(forKey: kSymbolInputEnabled) == nil { + UserDefaults.standard.set(mgrPrefs.symbolInputEnabled, forKey: kSymbolInputEnabled) + } + + // 預設選字窗字詞文字尺寸,設成 18 剛剛好 + if UserDefaults.standard.object(forKey: kCandidateListTextSize) == nil { + UserDefaults.standard.set( + mgrPrefs.candidateListTextSize, forKey: kCandidateListTextSize) + } + + // 預設摁空格鍵來選字,所以設成 true + if UserDefaults.standard.object(forKey: kChooseCandidateUsingSpace) == nil { + UserDefaults.standard.set( + mgrPrefs.chooseCandidateUsingSpace, forKey: kChooseCandidateUsingSpace) + } + + // 自動檢測使用者自訂語彙數據的變動並載入。 + if UserDefaults.standard.object(forKey: kShouldAutoReloadUserDataFiles) == nil { + UserDefaults.standard.set( + mgrPrefs.shouldAutoReloadUserDataFiles, forKey: kShouldAutoReloadUserDataFiles) + } + + // 預設情況下讓 Tab 鍵在選字窗內切換候選字、而不是用來換頁。 + if UserDefaults.standard.object(forKey: kSpecifyTabKeyBehavior) == nil { + UserDefaults.standard.set( + mgrPrefs.specifyTabKeyBehavior, forKey: kSpecifyTabKeyBehavior) + } + + // 預設情況下讓 Space 鍵在選字窗內切換候選字、而不是用來換頁。 + if UserDefaults.standard.object(forKey: kSpecifySpaceKeyBehavior) == nil { + UserDefaults.standard.set( + mgrPrefs.specifySpaceKeyBehavior, forKey: kSpecifySpaceKeyBehavior) + } + + // 預設禁用逐字選字模式(就是每個字都要選的那種),所以設成 false + if UserDefaults.standard.object(forKey: kUseSCPCTypingMode) == nil { + UserDefaults.standard.set(mgrPrefs.useSCPCTypingMode, forKey: kUseSCPCTypingMode) + } + + // 預設禁用逐字選字模式時的聯想詞功能,所以設成 false + if UserDefaults.standard.object(forKey: kAssociatedPhrasesEnabled) == nil { + UserDefaults.standard.set( + mgrPrefs.associatedPhrasesEnabled, forKey: kAssociatedPhrasesEnabled) + } + + // 預設漢音風格選字,所以要設成 0 + if UserDefaults.standard.object(forKey: kSelectPhraseAfterCursorAsCandidatePreference) + == nil + { + UserDefaults.standard.set( + mgrPrefs.selectPhraseAfterCursorAsCandidate, + forKey: kSelectPhraseAfterCursorAsCandidatePreference) + } + + // 預設在選字後自動移動游標 + if UserDefaults.standard.object(forKey: kMoveCursorAfterSelectingCandidate) == nil { + UserDefaults.standard.set( + mgrPrefs.moveCursorAfterSelectingCandidate, + forKey: kMoveCursorAfterSelectingCandidate) + } + + // 預設橫向選字窗,不爽請自行改成縱向選字窗 + if UserDefaults.standard.object(forKey: kUseHorizontalCandidateListPreference) == nil { + UserDefaults.standard.set( + mgrPrefs.useHorizontalCandidateList, forKey: kUseHorizontalCandidateListPreference) + } + + // 預設停用全字庫支援 + if UserDefaults.standard.object(forKey: kCNS11643Enabled) == nil { + UserDefaults.standard.set(mgrPrefs.cns11643Enabled, forKey: kCNS11643Enabled) + } + + // 預設停用繁體轉康熙模組 + if UserDefaults.standard.object(forKey: kChineseConversionEnabled) == nil { + UserDefaults.standard.set( + mgrPrefs.chineseConversionEnabled, forKey: kChineseConversionEnabled) + } + + // 預設停用繁體轉 JIS 當用新字體模組 + if UserDefaults.standard.object(forKey: kShiftJISShinjitaiOutputEnabled) == nil { + UserDefaults.standard.set( + mgrPrefs.shiftJISShinjitaiOutputEnabled, forKey: kShiftJISShinjitaiOutputEnabled) + } + + // 預設停用自訂語彙置換 + if UserDefaults.standard.object(forKey: kPhraseReplacementEnabled) == nil { + UserDefaults.standard.set( + mgrPrefs.phraseReplacementEnabled, forKey: kPhraseReplacementEnabled) + } + + // 預設沒事不要在那裡放屁 + if UserDefaults.standard.object(forKey: kShouldNotFartInLieuOfBeep) == nil { + UserDefaults.standard.set( + mgrPrefs.shouldNotFartInLieuOfBeep, forKey: kShouldNotFartInLieuOfBeep) + } + + UserDefaults.standard.synchronize() + } + + @UserDefault(key: kIsDebugModeEnabled, defaultValue: false) + @objc static var isDebugModeEnabled: Bool + + @UserDefault(key: kUserDataFolderSpecified, defaultValue: "") + @objc static var userDataFolderSpecified: String + + @objc static func ifSpecifiedUserDataPathExistsInPlist() -> Bool { + UserDefaults.standard.object(forKey: kUserDataFolderSpecified) != nil + } + + @UserDefault(key: kAppleLanguagesPreferences, defaultValue: []) + @objc static var appleLanguages: [String] + + @UserDefault(key: kKeyboardLayoutPreference, defaultValue: 0) + @objc static var keyboardLayout: Int + + @objc static var keyboardLayoutName: String { + (KeyboardLayout(rawValue: self.keyboardLayout) ?? KeyboardLayout.standard).name + } + + @UserDefault( + key: kBasisKeyboardLayoutPreference, defaultValue: "com.apple.keylayout.ZhuyinBopomofo") + @objc static var basisKeyboardLayout: String + + @UserDefault(key: kShowPageButtonsInCandidateWindow, defaultValue: true) + @objc static var showPageButtonsInCandidateWindow: Bool + + @CandidateListTextSize(key: kCandidateListTextSize) + @objc static var candidateListTextSize: CGFloat + + @UserDefault(key: kShouldAutoReloadUserDataFiles, defaultValue: true) + @objc static var shouldAutoReloadUserDataFiles: Bool + + @UserDefault(key: kSelectPhraseAfterCursorAsCandidatePreference, defaultValue: false) + @objc static var selectPhraseAfterCursorAsCandidate: Bool + + @UserDefault(key: kMoveCursorAfterSelectingCandidate, defaultValue: false) + @objc static var moveCursorAfterSelectingCandidate: Bool + + @UserDefault(key: kUseHorizontalCandidateListPreference, defaultValue: true) + @objc static var useHorizontalCandidateList: Bool + + @ComposingBufferSize(key: kComposingBufferSizePreference) + @objc static var composingBufferSize: Int + + @UserDefault(key: kChooseCandidateUsingSpace, defaultValue: true) + @objc static var chooseCandidateUsingSpace: Bool + + @UserDefault(key: kUseSCPCTypingMode, defaultValue: false) + @objc static var useSCPCTypingMode: Bool + + @objc static func toggleSCPCTypingModeEnabled() -> Bool { + useSCPCTypingMode = !useSCPCTypingMode + UserDefaults.standard.set(useSCPCTypingMode, forKey: kUseSCPCTypingMode) + return useSCPCTypingMode + } + + @UserDefault(key: kMaxCandidateLength, defaultValue: kDefaultComposingBufferSize * 2) + @objc static var maxCandidateLength: Int + + @UserDefault(key: kShouldNotFartInLieuOfBeep, defaultValue: true) + @objc static var shouldNotFartInLieuOfBeep: Bool + + @objc static func toggleShouldNotFartInLieuOfBeep() -> Bool { + shouldNotFartInLieuOfBeep = !shouldNotFartInLieuOfBeep + UserDefaults.standard.set(shouldNotFartInLieuOfBeep, forKey: kShouldNotFartInLieuOfBeep) + return shouldNotFartInLieuOfBeep + } + + @UserDefault(key: kCNS11643Enabled, defaultValue: false) + @objc static var cns11643Enabled: Bool + + @objc static func toggleCNS11643Enabled() -> Bool { + cns11643Enabled = !cns11643Enabled + mgrLangModel.setCNSEnabled(cns11643Enabled) // 很重要 + UserDefaults.standard.set(cns11643Enabled, forKey: kCNS11643Enabled) + return cns11643Enabled + } + + @UserDefault(key: kSymbolInputEnabled, defaultValue: true) + @objc static var symbolInputEnabled: Bool + + @objc static func toggleSymbolInputEnabled() -> Bool { + symbolInputEnabled = !symbolInputEnabled + mgrLangModel.setSymbolEnabled(symbolInputEnabled) // 很重要 + UserDefaults.standard.set(symbolInputEnabled, forKey: kSymbolInputEnabled) + return symbolInputEnabled + } + + @UserDefault(key: kChineseConversionEnabled, defaultValue: false) + @objc static var chineseConversionEnabled: Bool + + @objc @discardableResult static func toggleChineseConversionEnabled() -> Bool { + chineseConversionEnabled = !chineseConversionEnabled + // 康熙轉換與 JIS 轉換不能同時開啟,否則會出現某些奇奇怪怪的情況 + if chineseConversionEnabled && shiftJISShinjitaiOutputEnabled { + self.toggleShiftJISShinjitaiOutputEnabled() + UserDefaults.standard.set( + shiftJISShinjitaiOutputEnabled, forKey: kShiftJISShinjitaiOutputEnabled) + } + UserDefaults.standard.set(chineseConversionEnabled, forKey: kChineseConversionEnabled) + return chineseConversionEnabled + } + + @UserDefault(key: kShiftJISShinjitaiOutputEnabled, defaultValue: false) + @objc static var shiftJISShinjitaiOutputEnabled: Bool + + @objc @discardableResult static func toggleShiftJISShinjitaiOutputEnabled() -> Bool { + shiftJISShinjitaiOutputEnabled = !shiftJISShinjitaiOutputEnabled + // 康熙轉換與 JIS 轉換不能同時開啟,否則會出現某些奇奇怪怪的情況 + if shiftJISShinjitaiOutputEnabled && chineseConversionEnabled { + self.toggleChineseConversionEnabled() + } + UserDefaults.standard.set( + shiftJISShinjitaiOutputEnabled, forKey: kShiftJISShinjitaiOutputEnabled) + return shiftJISShinjitaiOutputEnabled + } + + @UserDefault(key: kHalfWidthPunctuationEnabled, defaultValue: false) + @objc static var halfWidthPunctuationEnabled: Bool + + @objc static func toggleHalfWidthPunctuationEnabled() -> Bool { + halfWidthPunctuationEnabled = !halfWidthPunctuationEnabled + return halfWidthPunctuationEnabled + } + + @UserDefault(key: kEscToCleanInputBuffer, defaultValue: true) + @objc static var escToCleanInputBuffer: Bool + + @UserDefault(key: kSpecifyTabKeyBehavior, defaultValue: false) + @objc static var specifyTabKeyBehavior: Bool + + @UserDefault(key: kSpecifySpaceKeyBehavior, defaultValue: false) + @objc static var specifySpaceKeyBehavior: Bool + + // MARK: - Optional settings + @UserDefault(key: kCandidateTextFontName, defaultValue: nil) + @objc static var candidateTextFontName: String? + + @UserDefault(key: kCandidateKeyLabelFontName, defaultValue: nil) + @objc static var candidateKeyLabelFontName: String? + + @UserDefault(key: kCandidateKeys, defaultValue: kDefaultKeys) + @objc static var candidateKeys: String + + @objc static var defaultCandidateKeys: String { + kDefaultKeys + } + @objc static var suggestedCandidateKeys: [String] { + [kDefaultKeys, "234567890", "QWERTYUIO", "QWERTASDF", "ASDFGHJKL", "ASDFZXCVB"] + } + + @objc static func validate(candidateKeys: String) throws { + let trimmed = candidateKeys.trimmingCharacters(in: .whitespacesAndNewlines) + if trimmed.isEmpty { + throw CandidateKeyError.empty + } + if !trimmed.canBeConverted(to: .ascii) { + throw CandidateKeyError.invalidCharacters + } + if trimmed.contains(" ") { + throw CandidateKeyError.containSpace + } + if trimmed.count < 4 { + throw CandidateKeyError.tooShort + } + if trimmed.count > 15 { + throw CandidateKeyError.tooLong + } + let set = Set(Array(trimmed)) + if set.count != trimmed.count { + throw CandidateKeyError.duplicatedCharacters + } + } + + enum CandidateKeyError: Error, LocalizedError { + case empty + case invalidCharacters + case containSpace + case duplicatedCharacters + case tooShort + case tooLong + + var errorDescription: String? { + switch self { + case .empty: + return NSLocalizedString("Candidates keys cannot be empty.", comment: "") + case .invalidCharacters: + return NSLocalizedString( + "Candidate keys can only contain ASCII characters like alphanumericals.", + comment: "") + case .containSpace: + return NSLocalizedString("Candidate keys cannot contain space.", comment: "") + case .duplicatedCharacters: + return NSLocalizedString("There should not be duplicated keys.", comment: "") + case .tooShort: + return NSLocalizedString( + "Please specify at least 4 candidate keys.", comment: "") + case .tooLong: + return NSLocalizedString("Maximum 15 candidate keys allowed.", comment: "") + } + } + + } + + @UserDefault(key: kPhraseReplacementEnabled, defaultValue: false) + @objc static var phraseReplacementEnabled: Bool + + @objc static func togglePhraseReplacementEnabled() -> Bool { + phraseReplacementEnabled = !phraseReplacementEnabled + mgrLangModel.setPhraseReplacementEnabled(phraseReplacementEnabled) + UserDefaults.standard.set(phraseReplacementEnabled, forKey: kPhraseReplacementEnabled) + return phraseReplacementEnabled + } + + @UserDefault(key: kAssociatedPhrasesEnabled, defaultValue: false) + @objc static var associatedPhrasesEnabled: Bool + + @objc static func toggleAssociatedPhrasesEnabled() -> Bool { + associatedPhrasesEnabled = !associatedPhrasesEnabled + UserDefaults.standard.set(associatedPhrasesEnabled, forKey: kAssociatedPhrasesEnabled) + return associatedPhrasesEnabled + } } diff --git a/Source/Modules/LangModelRelated/mgrLangModel.mm b/Source/Modules/LangModelRelated/mgrLangModel.mm index 9d5e411d..3ca207ba 100644 --- a/Source/Modules/LangModelRelated/mgrLangModel.mm +++ b/Source/Modules/LangModelRelated/mgrLangModel.mm @@ -235,7 +235,7 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing [currentMarkedPhrase appendString:userPhrase]; if (areWeDuplicating && !areWeDeleting) { // Do not use ASCII characters to comment here. - // Otherwise, it will be scrambled by HYPY2BPMF module shipped in the vChewing Phrase Editor. + // Otherwise, it will be scrambled by cnvHYPYtoBPMF module shipped in the vChewing Phrase Editor. [currentMarkedPhrase appendString:@"\t#𝙾𝚟𝚎𝚛𝚛𝚒𝚍𝚎"]; } [currentMarkedPhrase appendString:@"\n"]; diff --git a/Source/Modules/SFX/clsSFX.swift b/Source/Modules/SFX/clsSFX.swift index 5f4de74f..6b2f2d1e 100644 --- a/Source/Modules/SFX/clsSFX.swift +++ b/Source/Modules/SFX/clsSFX.swift @@ -1,59 +1,65 @@ // Copyright (c) 2022 and onwards Isaac Xen (MIT License). // All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ import Cocoa @objc public class clsSFX: NSObject, NSSoundDelegate { - private static let shared = clsSFX() - private override init(){ - super.init() - } - private var currentBeep: NSSound? - private func beep() { - // Stop existing beep - if let beep = currentBeep { - if beep.isPlaying { - beep.stop() - } - } - // Create a new beep sound if possible - var sndBeep:String - if mgrPrefs.shouldNotFartInLieuOfBeep == false { - sndBeep = "Fart" - } else { - sndBeep = "Beep" - } - guard - let beep = NSSound(named:sndBeep) - else { - NSSound.beep() - return - } - beep.delegate = self - beep.volume = 0.4 - beep.play() - currentBeep = beep - } - @objc public func sound(_ sound: NSSound, didFinishPlaying flag: Bool) { - currentBeep = nil - } - @objc static func beep() { - shared.beep() - } + private static let shared = clsSFX() + private override init() { + super.init() + } + private var currentBeep: NSSound? + private func beep() { + // Stop existing beep + if let beep = currentBeep { + if beep.isPlaying { + beep.stop() + } + } + // Create a new beep sound if possible + var sndBeep: String + if mgrPrefs.shouldNotFartInLieuOfBeep == false { + sndBeep = "Fart" + } else { + sndBeep = "Beep" + } + guard + let beep = NSSound(named: sndBeep) + else { + NSSound.beep() + return + } + beep.delegate = self + beep.volume = 0.4 + beep.play() + currentBeep = beep + } + @objc public func sound(_ sound: NSSound, didFinishPlaying flag: Bool) { + currentBeep = nil + } + @objc static func beep() { + shared.beep() + } } diff --git a/Source/Modules/main.swift b/Source/Modules/main.swift index 9996268f..185e29bf 100644 --- a/Source/Modules/main.swift +++ b/Source/Modules/main.swift @@ -1,20 +1,27 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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 @@ -23,30 +30,32 @@ import InputMethodKit let kConnectionName = "vChewing_1_Connection" if CommandLine.arguments.count > 1 { - if CommandLine.arguments[1] == "install" { - let exitCode = IME.registerInputMethod() - exit(exitCode) - } - if CommandLine.arguments[1] == "uninstall" { - let exitCode = IME.uninstall(isSudo: IME.isSudoMode) - exit(exitCode) - } + if CommandLine.arguments[1] == "install" { + let exitCode = IME.registerInputMethod() + exit(exitCode) + } + if CommandLine.arguments[1] == "uninstall" { + let exitCode = IME.uninstall(isSudo: IME.isSudoMode) + exit(exitCode) + } } guard let mainNibName = Bundle.main.infoDictionary?["NSMainNibFile"] as? String else { - NSLog("Fatal error: NSMainNibFile key not defined in Info.plist."); - exit(-1) + NSLog("Fatal error: NSMainNibFile key not defined in Info.plist.") + exit(-1) } let loaded = Bundle.main.loadNibNamed(mainNibName, owner: NSApp, topLevelObjects: nil) if !loaded { - NSLog("Fatal error: Cannot load \(mainNibName).") - exit(-1) + NSLog("Fatal error: Cannot load \(mainNibName).") + exit(-1) } -guard let bundleID = Bundle.main.bundleIdentifier, let server = IMKServer(name: kConnectionName, bundleIdentifier: bundleID) else { - NSLog("Fatal error: Cannot initialize input method server with connection \(kConnectionName).") - exit(-1) +guard let bundleID = Bundle.main.bundleIdentifier, + let server = IMKServer(name: kConnectionName, bundleIdentifier: bundleID) +else { + NSLog("Fatal error: Cannot initialize input method server with connection \(kConnectionName).") + exit(-1) } NSApp.run() diff --git a/Source/UI/CandidateUI/CandidateController.swift b/Source/UI/CandidateUI/CandidateController.swift index eaabad44..2ef1ce09 100644 --- a/Source/UI/CandidateUI/CandidateController.swift +++ b/Source/UI/CandidateUI/CandidateController.swift @@ -1,165 +1,176 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ import Cocoa @objc(VTCandidateKeyLabel) public class CandidateKeyLabel: NSObject { - @objc public private(set) var key: String - @objc public private(set) var displayedText: String + @objc public private(set) var key: String + @objc public private(set) var displayedText: String - public init(key: String, displayedText: String) { - self.key = key - self.displayedText = displayedText - super.init() - } + public init(key: String, displayedText: String) { + self.key = key + self.displayedText = displayedText + super.init() + } } @objc(VTCandidateControllerDelegate) public protocol CandidateControllerDelegate: AnyObject { - func candidateCountForController(_ controller: CandidateController) -> UInt - func candidateController(_ controller: CandidateController, candidateAtIndex index: UInt) -> String - func candidateController(_ controller: CandidateController, didSelectCandidateAtIndex index: UInt) + func candidateCountForController(_ controller: CandidateController) -> UInt + func candidateController(_ controller: CandidateController, candidateAtIndex index: UInt) + -> String + func candidateController( + _ controller: CandidateController, didSelectCandidateAtIndex index: UInt) } @objc(VTCandidateController) public class CandidateController: NSWindowController { - @objc public weak var delegate: CandidateControllerDelegate? { - didSet { - reloadData() - } - } - @objc public var selectedCandidateIndex: UInt = UInt.max - @objc public var visible: Bool = false { - didSet { - NSObject.cancelPreviousPerformRequests(withTarget: self) - if visible { - window?.perform(#selector(NSWindow.orderFront(_:)), with: self, afterDelay: 0.0) - } else { - window?.perform(#selector(NSWindow.orderOut(_:)), with: self, afterDelay: 0.0) - } - } - } - @objc public var windowTopLeftPoint: NSPoint { - get { - guard let frameRect = window?.frame else { - return NSPoint.zero - } - return NSPoint(x: frameRect.minX, y: frameRect.maxY) - } - set { - DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) { - self.set(windowTopLeftPoint: newValue, bottomOutOfScreenAdjustmentHeight: 0) - } - } - } + @objc public weak var delegate: CandidateControllerDelegate? { + didSet { + reloadData() + } + } + @objc public var selectedCandidateIndex: UInt = UInt.max + @objc public var visible: Bool = false { + didSet { + NSObject.cancelPreviousPerformRequests(withTarget: self) + if visible { + window?.perform(#selector(NSWindow.orderFront(_:)), with: self, afterDelay: 0.0) + } else { + window?.perform(#selector(NSWindow.orderOut(_:)), with: self, afterDelay: 0.0) + } + } + } + @objc public var windowTopLeftPoint: NSPoint { + get { + guard let frameRect = window?.frame else { + return NSPoint.zero + } + return NSPoint(x: frameRect.minX, y: frameRect.maxY) + } + set { + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) { + self.set(windowTopLeftPoint: newValue, bottomOutOfScreenAdjustmentHeight: 0) + } + } + } - @objc public var keyLabels: [CandidateKeyLabel] = ["1", "2", "3", "4", "5", "6", "7", "8", "9"].map { - CandidateKeyLabel(key: $0, displayedText: $0) - } - @objc public var keyLabelFont: NSFont = NSFont.monospacedDigitSystemFont(ofSize: 14, weight: .medium) - @objc public var candidateFont: NSFont = NSFont.systemFont(ofSize: 18) - @objc public var tooltip: String = "" + @objc public var keyLabels: [CandidateKeyLabel] = ["1", "2", "3", "4", "5", "6", "7", "8", "9"] + .map { + CandidateKeyLabel(key: $0, displayedText: $0) + } + @objc public var keyLabelFont: NSFont = NSFont.monospacedDigitSystemFont( + ofSize: 14, weight: .medium) + @objc public var candidateFont: NSFont = NSFont.systemFont(ofSize: 18) + @objc public var tooltip: String = "" - @objc public func reloadData() { - } + @objc public func reloadData() { + } - @objc public func showNextPage() -> Bool { - false - } + @objc public func showNextPage() -> Bool { + false + } - @objc public func showPreviousPage() -> Bool { - false - } + @objc public func showPreviousPage() -> Bool { + false + } - @objc public func highlightNextCandidate() -> Bool { - false - } + @objc public func highlightNextCandidate() -> Bool { + false + } - @objc public func highlightPreviousCandidate() -> Bool { - false - } + @objc public func highlightPreviousCandidate() -> Bool { + false + } - @objc public func candidateIndexAtKeyLabelIndex(_ index: UInt) -> UInt { - UInt.max - } + @objc public func candidateIndexAtKeyLabelIndex(_ index: UInt) -> UInt { + UInt.max + } - /// Sets the location of the candidate window. - /// - /// Please note that the method has side effects that modifies - /// `windowTopLeftPoint` to make the candidate window to stay in at least - /// in a screen. - /// - /// - Parameters: - /// - windowTopLeftPoint: The given location. - /// - height: The height that helps the window not to be out of the bottom - /// of a screen. - @objc(setWindowTopLeftPoint:bottomOutOfScreenAdjustmentHeight:) - public func set(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight height: CGFloat) { - DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) { - self.doSet(windowTopLeftPoint: windowTopLeftPoint, bottomOutOfScreenAdjustmentHeight: height) - } - } + /// Sets the location of the candidate window. + /// + /// Please note that the method has side effects that modifies + /// `windowTopLeftPoint` to make the candidate window to stay in at least + /// in a screen. + /// + /// - Parameters: + /// - windowTopLeftPoint: The given location. + /// - height: The height that helps the window not to be out of the bottom + /// of a screen. + @objc(setWindowTopLeftPoint:bottomOutOfScreenAdjustmentHeight:) + public func set(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight height: CGFloat) { + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) { + self.doSet( + windowTopLeftPoint: windowTopLeftPoint, bottomOutOfScreenAdjustmentHeight: height) + } + } - func doSet(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight height: CGFloat) { - var adjustedPoint = windowTopLeftPoint - var adjustedHeight = height + func doSet(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight height: CGFloat) { + var adjustedPoint = windowTopLeftPoint + var adjustedHeight = height - var screenFrame = NSScreen.main?.visibleFrame ?? NSRect.zero - for screen in NSScreen.screens { - let frame = screen.visibleFrame - if windowTopLeftPoint.x >= frame.minX && - windowTopLeftPoint.x <= frame.maxX && - windowTopLeftPoint.y >= frame.minY && - windowTopLeftPoint.y <= frame.maxY { - screenFrame = frame - break - } - } + var screenFrame = NSScreen.main?.visibleFrame ?? NSRect.zero + for screen in NSScreen.screens { + let frame = screen.visibleFrame + if windowTopLeftPoint.x >= frame.minX && windowTopLeftPoint.x <= frame.maxX + && windowTopLeftPoint.y >= frame.minY && windowTopLeftPoint.y <= frame.maxY + { + screenFrame = frame + break + } + } - if adjustedHeight > screenFrame.size.height / 2.0 { - adjustedHeight = 0.0 - } + if adjustedHeight > screenFrame.size.height / 2.0 { + adjustedHeight = 0.0 + } - let windowSize = window?.frame.size ?? NSSize.zero + let windowSize = window?.frame.size ?? NSSize.zero - // bottom beneath the screen? - if adjustedPoint.y - windowSize.height < screenFrame.minY { - adjustedPoint.y = windowTopLeftPoint.y + adjustedHeight + windowSize.height - } + // bottom beneath the screen? + if adjustedPoint.y - windowSize.height < screenFrame.minY { + adjustedPoint.y = windowTopLeftPoint.y + adjustedHeight + windowSize.height + } - // top over the screen? - if adjustedPoint.y >= screenFrame.maxY { - adjustedPoint.y = screenFrame.maxY - 1.0 - } + // top over the screen? + if adjustedPoint.y >= screenFrame.maxY { + adjustedPoint.y = screenFrame.maxY - 1.0 + } - // right - if adjustedPoint.x + windowSize.width >= screenFrame.maxX { - adjustedPoint.x = screenFrame.maxX - windowSize.width - } + // right + if adjustedPoint.x + windowSize.width >= screenFrame.maxX { + adjustedPoint.x = screenFrame.maxX - windowSize.width + } - // left - if adjustedPoint.x < screenFrame.minX { - adjustedPoint.x = screenFrame.minX - } + // left + if adjustedPoint.x < screenFrame.minX { + adjustedPoint.x = screenFrame.minX + } - window?.setFrameTopLeftPoint(adjustedPoint) - } + window?.setFrameTopLeftPoint(adjustedPoint) + } } diff --git a/Source/UI/CandidateUI/HorizontalCandidateController.swift b/Source/UI/CandidateUI/HorizontalCandidateController.swift index bdcb39d5..d6f2be0a 100644 --- a/Source/UI/CandidateUI/HorizontalCandidateController.swift +++ b/Source/UI/CandidateUI/HorizontalCandidateController.swift @@ -1,411 +1,462 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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 -fileprivate class HorizontalCandidateView: NSView { - var highlightedIndex: UInt = 0 - var action: Selector? - weak var target: AnyObject? +private class HorizontalCandidateView: NSView { + var highlightedIndex: UInt = 0 + var action: Selector? + weak var target: AnyObject? - private var keyLabels: [String] = [] - private var displayedCandidates: [String] = [] - private var dispCandidatesWithLabels: [String] = [] - private var keyLabelHeight: CGFloat = 0 - private var keyLabelWidth: CGFloat = 0 - private var candidateTextHeight: CGFloat = 0 - private var cellPadding: CGFloat = 0 - private var keyLabelAttrDict: [NSAttributedString.Key: AnyObject] = [:] - private var candidateAttrDict: [NSAttributedString.Key: AnyObject] = [:] - private var candidateWithLabelAttrDict: [NSAttributedString.Key: AnyObject] = [:] - private var elementWidths: [CGFloat] = [] - private var trackingHighlightedIndex: UInt = UInt.max + private var keyLabels: [String] = [] + private var displayedCandidates: [String] = [] + private var dispCandidatesWithLabels: [String] = [] + private var keyLabelHeight: CGFloat = 0 + private var keyLabelWidth: CGFloat = 0 + private var candidateTextHeight: CGFloat = 0 + private var cellPadding: CGFloat = 0 + private var keyLabelAttrDict: [NSAttributedString.Key: AnyObject] = [:] + private var candidateAttrDict: [NSAttributedString.Key: AnyObject] = [:] + private var candidateWithLabelAttrDict: [NSAttributedString.Key: AnyObject] = [:] + private var elementWidths: [CGFloat] = [] + private var trackingHighlightedIndex: UInt = UInt.max - override var isFlipped: Bool { - true - } + override var isFlipped: Bool { + true + } - var sizeForView: NSSize { - var result = NSSize.zero + var sizeForView: NSSize { + var result = NSSize.zero - if !elementWidths.isEmpty { - result.width = elementWidths.reduce(0, +) - result.width += CGFloat(elementWidths.count) - result.height = candidateTextHeight + cellPadding - } - return result - } + if !elementWidths.isEmpty { + result.width = elementWidths.reduce(0, +) + result.width += CGFloat(elementWidths.count) + result.height = candidateTextHeight + cellPadding + } + return result + } - @objc(setKeyLabels:displayedCandidates:) - func set(keyLabels labels: [String], displayedCandidates candidates: [String]) { - let count = min(labels.count, candidates.count) - keyLabels = Array(labels[0.. UInt? { - let location = convert(event.locationInWindow, to: nil) - if !NSPointInRect(location, self.bounds) { - return nil - } - var accuWidth: CGFloat = 0.0 - for index in 0..= accuWidth && location.x <= accuWidth + currentWidth { - return UInt(index) - } - accuWidth += currentWidth + 1.0 - } - return nil + var activeCandidateIndexAttr = keyLabelAttrDict + var activeCandidateAttr = candidateAttrDict + if index == highlightedIndex { + let colorBlendAmount: CGFloat = IME.isDarkMode() ? 0.25 : 0 + // The background color of the highlightened candidate + switch ctlInputMethod.currentKeyHandler.inputMode { + case InputMode.imeModeCHS: + NSColor.systemRed.blended( + withFraction: colorBlendAmount, + of: NSColor.controlBackgroundColor)! + .setFill() + case InputMode.imeModeCHT: + NSColor.systemBlue.blended( + withFraction: colorBlendAmount, + of: NSColor.controlBackgroundColor)! + .setFill() + default: + NSColor.alternateSelectedControlColor.setFill() + } + // Highlightened index text color + activeCandidateIndexAttr[.foregroundColor] = NSColor.selectedMenuItemTextColor + .withAlphaComponent(0.84) + // Highlightened phrase text color + activeCandidateAttr[.foregroundColor] = NSColor.selectedMenuItemTextColor + } else { + NSColor.controlBackgroundColor.setFill() + } + switch ctlInputMethod.currentKeyHandler.inputMode { + case InputMode.imeModeCHS: + if #available(macOS 12.0, *) { + activeCandidateAttr[.languageIdentifier] = "zh-Hans" as AnyObject + } + case InputMode.imeModeCHT: + if #available(macOS 12.0, *) { + activeCandidateAttr[.languageIdentifier] = "zh-Hant" as AnyObject + } + default: + break + } + NSBezierPath.fill(rctCandidateArea) + (keyLabels[index] as NSString).draw( + in: rctLabel, withAttributes: activeCandidateIndexAttr) + (displayedCandidates[index] as NSString).draw( + in: rctCandidatePhrase, withAttributes: activeCandidateAttr) + accuWidth += currentWidth + 1.0 + } + } - } + private func findHitIndex(event: NSEvent) -> UInt? { + let location = convert(event.locationInWindow, to: nil) + if !NSPointInRect(location, self.bounds) { + return nil + } + var accuWidth: CGFloat = 0.0 + for index in 0..= accuWidth && location.x <= accuWidth + currentWidth { + return UInt(index) + } + accuWidth += currentWidth + 1.0 + } + return nil - override func mouseDown(with event: NSEvent) { - guard let newIndex = findHitIndex(event: event) else { - return - } - var triggerAction = false - if newIndex == highlightedIndex { - triggerAction = true - } else { - highlightedIndex = trackingHighlightedIndex - } + } - trackingHighlightedIndex = 0 - self.setNeedsDisplay(self.bounds) - if triggerAction { - if let target = target as? NSObject, let action = action { - target.perform(action, with: self) - } - } - } + override func mouseUp(with event: NSEvent) { + trackingHighlightedIndex = highlightedIndex + guard let newIndex = findHitIndex(event: event) else { + return + } + highlightedIndex = newIndex + self.setNeedsDisplay(self.bounds) + } + + override func mouseDown(with event: NSEvent) { + guard let newIndex = findHitIndex(event: event) else { + return + } + var triggerAction = false + if newIndex == highlightedIndex { + triggerAction = true + } else { + highlightedIndex = trackingHighlightedIndex + } + + trackingHighlightedIndex = 0 + self.setNeedsDisplay(self.bounds) + if triggerAction { + if let target = target as? NSObject, let action = action { + target.perform(action, with: self) + } + } + } } @objc(VTHorizontalCandidateController) public class HorizontalCandidateController: CandidateController { - private var candidateView: HorizontalCandidateView - private var prevPageButton: NSButton - private var nextPageButton: NSButton - private var currentPage: UInt = 0 + private var candidateView: HorizontalCandidateView + private var prevPageButton: NSButton + private var nextPageButton: NSButton + private var currentPage: UInt = 0 - public init() { - var contentRect = NSRect(x: 128.0, y: 128.0, width: 0.0, height: 0.0) - let styleMask: NSWindow.StyleMask = [.nonactivatingPanel] - let panel = NSPanel(contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false) - panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel) + 1) - panel.hasShadow = true - panel.isOpaque = false - panel.backgroundColor = NSColor.clear - - contentRect.origin = NSPoint.zero - candidateView = HorizontalCandidateView(frame: contentRect) + public init() { + var contentRect = NSRect(x: 128.0, y: 128.0, width: 0.0, height: 0.0) + let styleMask: NSWindow.StyleMask = [.nonactivatingPanel] + let panel = NSPanel( + contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false) + panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel) + 1) + panel.hasShadow = true + panel.isOpaque = false + panel.backgroundColor = NSColor.clear - candidateView.wantsLayer = true - candidateView.layer?.borderColor = NSColor.selectedMenuItemTextColor.withAlphaComponent(0.30).cgColor - candidateView.layer?.borderWidth = 1.0 - if #available(macOS 10.13, *) { - candidateView.layer?.cornerRadius = 6.0 - } + contentRect.origin = NSPoint.zero + candidateView = HorizontalCandidateView(frame: contentRect) - panel.contentView?.addSubview(candidateView) - - contentRect.size = NSSize(width: 20.0, height: 10.0) // Reduce the button width - let buttonAttribute: [NSAttributedString.Key : Any] = [.font : NSFont.systemFont(ofSize: 9.0)] + candidateView.wantsLayer = true + candidateView.layer?.borderColor = + NSColor.selectedMenuItemTextColor.withAlphaComponent(0.30).cgColor + candidateView.layer?.borderWidth = 1.0 + if #available(macOS 10.13, *) { + candidateView.layer?.cornerRadius = 6.0 + } - nextPageButton = NSButton(frame: contentRect) - NSColor.controlBackgroundColor.setFill() - NSBezierPath.fill(nextPageButton.bounds) - nextPageButton.wantsLayer = true - nextPageButton.layer?.masksToBounds = true - nextPageButton.layer?.borderColor = NSColor.clear.cgColor - nextPageButton.layer?.borderWidth = 0.0 - nextPageButton.setButtonType(.momentaryLight) - nextPageButton.bezelStyle = .disclosure - nextPageButton.userInterfaceLayoutDirection = .leftToRight - nextPageButton.attributedTitle = NSMutableAttributedString(string: " ", attributes: buttonAttribute) // Next Page Arrow - prevPageButton = NSButton(frame: contentRect) - NSColor.controlBackgroundColor.setFill() - NSBezierPath.fill(prevPageButton.bounds) - prevPageButton.wantsLayer = true - prevPageButton.layer?.masksToBounds = true - prevPageButton.layer?.borderColor = NSColor.clear.cgColor - prevPageButton.layer?.borderWidth = 0.0 - prevPageButton.setButtonType(.momentaryLight) - prevPageButton.bezelStyle = .disclosure - prevPageButton.userInterfaceLayoutDirection = .rightToLeft - prevPageButton.attributedTitle = NSMutableAttributedString(string: " ", attributes: buttonAttribute) // Previous Page Arrow - panel.contentView?.addSubview(nextPageButton) - panel.contentView?.addSubview(prevPageButton) + panel.contentView?.addSubview(candidateView) - super.init(window: panel) + contentRect.size = NSSize(width: 20.0, height: 10.0) // Reduce the button width + let buttonAttribute: [NSAttributedString.Key: Any] = [.font: NSFont.systemFont(ofSize: 9.0)] - candidateView.target = self - candidateView.action = #selector(candidateViewMouseDidClick(_:)) + nextPageButton = NSButton(frame: contentRect) + NSColor.controlBackgroundColor.setFill() + NSBezierPath.fill(nextPageButton.bounds) + nextPageButton.wantsLayer = true + nextPageButton.layer?.masksToBounds = true + nextPageButton.layer?.borderColor = NSColor.clear.cgColor + nextPageButton.layer?.borderWidth = 0.0 + nextPageButton.setButtonType(.momentaryLight) + nextPageButton.bezelStyle = .disclosure + nextPageButton.userInterfaceLayoutDirection = .leftToRight + nextPageButton.attributedTitle = NSMutableAttributedString( + string: " ", attributes: buttonAttribute) // Next Page Arrow + prevPageButton = NSButton(frame: contentRect) + NSColor.controlBackgroundColor.setFill() + NSBezierPath.fill(prevPageButton.bounds) + prevPageButton.wantsLayer = true + prevPageButton.layer?.masksToBounds = true + prevPageButton.layer?.borderColor = NSColor.clear.cgColor + prevPageButton.layer?.borderWidth = 0.0 + prevPageButton.setButtonType(.momentaryLight) + prevPageButton.bezelStyle = .disclosure + prevPageButton.userInterfaceLayoutDirection = .rightToLeft + prevPageButton.attributedTitle = NSMutableAttributedString( + string: " ", attributes: buttonAttribute) // Previous Page Arrow + panel.contentView?.addSubview(nextPageButton) + panel.contentView?.addSubview(prevPageButton) - nextPageButton.target = self - nextPageButton.action = #selector(pageButtonAction(_:)) + super.init(window: panel) - prevPageButton.target = self - prevPageButton.action = #selector(pageButtonAction(_:)) - } + candidateView.target = self + candidateView.action = #selector(candidateViewMouseDidClick(_:)) - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } + nextPageButton.target = self + nextPageButton.action = #selector(pageButtonAction(_:)) - public override func reloadData() { - candidateView.highlightedIndex = 0 - currentPage = 0 - layoutCandidateView() - } + prevPageButton.target = self + prevPageButton.action = #selector(pageButtonAction(_:)) + } - public override func showNextPage() -> Bool { - guard delegate != nil else {return false} - if pageCount == 1 {return highlightNextCandidate()} - currentPage = (currentPage + 1 >= pageCount) ? 0 : currentPage + 1 - candidateView.highlightedIndex = 0 - layoutCandidateView() - return true - } + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } - public override func showPreviousPage() -> Bool { - guard delegate != nil else {return false} - if pageCount == 1 {return highlightPreviousCandidate()} - currentPage = (currentPage == 0) ? pageCount - 1 : currentPage - 1 - candidateView.highlightedIndex = 0 - layoutCandidateView() - return true - } + public override func reloadData() { + candidateView.highlightedIndex = 0 + currentPage = 0 + layoutCandidateView() + } - public override func highlightNextCandidate() -> Bool { - guard let delegate = delegate else {return false} - selectedCandidateIndex = (selectedCandidateIndex + 1 >= delegate.candidateCountForController(self)) ? 0 : selectedCandidateIndex + 1 - return true - } + public override func showNextPage() -> Bool { + guard delegate != nil else { return false } + if pageCount == 1 { return highlightNextCandidate() } + currentPage = (currentPage + 1 >= pageCount) ? 0 : currentPage + 1 + candidateView.highlightedIndex = 0 + layoutCandidateView() + return true + } - public override func highlightPreviousCandidate() -> Bool { - guard let delegate = delegate else {return false} - selectedCandidateIndex = (selectedCandidateIndex == 0) ? delegate.candidateCountForController(self) - 1 : selectedCandidateIndex - 1 - return true - } + public override func showPreviousPage() -> Bool { + guard delegate != nil else { return false } + if pageCount == 1 { return highlightPreviousCandidate() } + currentPage = (currentPage == 0) ? pageCount - 1 : currentPage - 1 + candidateView.highlightedIndex = 0 + layoutCandidateView() + return true + } - public override func candidateIndexAtKeyLabelIndex(_ index: UInt) -> UInt { - guard let delegate = delegate else { - return UInt.max - } + public override func highlightNextCandidate() -> Bool { + guard let delegate = delegate else { return false } + selectedCandidateIndex = + (selectedCandidateIndex + 1 >= delegate.candidateCountForController(self)) + ? 0 : selectedCandidateIndex + 1 + return true + } - let result = currentPage * UInt(keyLabels.count) + index - return result < delegate.candidateCountForController(self) ? result : UInt.max - } + public override func highlightPreviousCandidate() -> Bool { + guard let delegate = delegate else { return false } + selectedCandidateIndex = + (selectedCandidateIndex == 0) + ? delegate.candidateCountForController(self) - 1 : selectedCandidateIndex - 1 + return true + } - public override var selectedCandidateIndex: UInt { - get { - currentPage * UInt(keyLabels.count) + candidateView.highlightedIndex - } - set { - guard let delegate = delegate else { - return - } - let keyLabelCount = UInt(keyLabels.count) - if newValue < delegate.candidateCountForController(self) { - currentPage = newValue / keyLabelCount - candidateView.highlightedIndex = newValue % keyLabelCount - layoutCandidateView() - } - } - } + public override func candidateIndexAtKeyLabelIndex(_ index: UInt) -> UInt { + guard let delegate = delegate else { + return UInt.max + } + + let result = currentPage * UInt(keyLabels.count) + index + return result < delegate.candidateCountForController(self) ? result : UInt.max + } + + public override var selectedCandidateIndex: UInt { + get { + currentPage * UInt(keyLabels.count) + candidateView.highlightedIndex + } + set { + guard let delegate = delegate else { + return + } + let keyLabelCount = UInt(keyLabels.count) + if newValue < delegate.candidateCountForController(self) { + currentPage = newValue / keyLabelCount + candidateView.highlightedIndex = newValue % keyLabelCount + layoutCandidateView() + } + } + } } extension HorizontalCandidateController { - private var pageCount: UInt { - guard let delegate = delegate else { - return 0 - } - let totalCount = delegate.candidateCountForController(self) - let keyLabelCount = UInt(keyLabels.count) - return totalCount / keyLabelCount + ((totalCount % keyLabelCount) != 0 ? 1 : 0) - } + private var pageCount: UInt { + guard let delegate = delegate else { + return 0 + } + let totalCount = delegate.candidateCountForController(self) + let keyLabelCount = UInt(keyLabels.count) + return totalCount / keyLabelCount + ((totalCount % keyLabelCount) != 0 ? 1 : 0) + } - private func layoutCandidateView() { - guard let delegate = delegate else { - return - } + private func layoutCandidateView() { + guard let delegate = delegate else { + return + } - candidateView.set(keyLabelFont: keyLabelFont, candidateFont: candidateFont) - var candidates = [String]() - let count = delegate.candidateCountForController(self) - let keyLabelCount = UInt(keyLabels.count) + candidateView.set(keyLabelFont: keyLabelFont, candidateFont: candidateFont) + var candidates = [String]() + let count = delegate.candidateCountForController(self) + let keyLabelCount = UInt(keyLabels.count) - let begin = currentPage * keyLabelCount - for index in begin.. 1 && mgrPrefs.showPageButtonsInCandidateWindow { - var buttonRect = nextPageButton.frame - let spacing:CGFloat = 0.0 + if pageCount > 1 && mgrPrefs.showPageButtonsInCandidateWindow { + var buttonRect = nextPageButton.frame + let spacing: CGFloat = 0.0 - buttonRect.size.height = floor(newSize.height / 2) + buttonRect.size.height = floor(newSize.height / 2) - let buttonOriginY = (newSize.height - (buttonRect.size.height * 2.0 + spacing)) / 2.0 - buttonRect.origin = NSPoint(x: newSize.width, y: buttonOriginY) - nextPageButton.frame = buttonRect + let buttonOriginY = (newSize.height - (buttonRect.size.height * 2.0 + spacing)) / 2.0 + buttonRect.origin = NSPoint(x: newSize.width, y: buttonOriginY) + nextPageButton.frame = buttonRect - buttonRect.origin = NSPoint(x: newSize.width, y: buttonOriginY + buttonRect.size.height + spacing) - prevPageButton.frame = buttonRect + buttonRect.origin = NSPoint( + x: newSize.width, y: buttonOriginY + buttonRect.size.height + spacing) + prevPageButton.frame = buttonRect - newSize.width += 20 - nextPageButton.isHidden = false - prevPageButton.isHidden = false - } else { - nextPageButton.isHidden = true - prevPageButton.isHidden = true - } + newSize.width += 20 + nextPageButton.isHidden = false + prevPageButton.isHidden = false + } else { + nextPageButton.isHidden = true + prevPageButton.isHidden = true + } - frameRect = window?.frame ?? NSRect.zero + frameRect = window?.frame ?? NSRect.zero - let topLeftPoint = NSMakePoint(frameRect.origin.x, frameRect.origin.y + frameRect.size.height) - frameRect.size = newSize - frameRect.origin = NSMakePoint(topLeftPoint.x, topLeftPoint.y - frameRect.size.height) - self.window?.setFrame(frameRect, display: false) - candidateView.setNeedsDisplay(candidateView.bounds) - } + let topLeftPoint = NSMakePoint( + frameRect.origin.x, frameRect.origin.y + frameRect.size.height) + frameRect.size = newSize + frameRect.origin = NSMakePoint(topLeftPoint.x, topLeftPoint.y - frameRect.size.height) + self.window?.setFrame(frameRect, display: false) + candidateView.setNeedsDisplay(candidateView.bounds) + } - @objc fileprivate func pageButtonAction(_ sender: Any) { - guard let sender = sender as? NSButton else { - return - } - if sender == nextPageButton { - _ = showNextPage() - } else if sender == prevPageButton { - _ = showPreviousPage() - } - } + @objc fileprivate func pageButtonAction(_ sender: Any) { + guard let sender = sender as? NSButton else { + return + } + if sender == nextPageButton { + _ = showNextPage() + } else if sender == prevPageButton { + _ = showPreviousPage() + } + } - @objc fileprivate func candidateViewMouseDidClick(_ sender: Any) { - delegate?.candidateController(self, didSelectCandidateAtIndex: selectedCandidateIndex) - } + @objc fileprivate func candidateViewMouseDidClick(_ sender: Any) { + delegate?.candidateController(self, didSelectCandidateAtIndex: selectedCandidateIndex) + } } diff --git a/Source/UI/CandidateUI/VerticalCandidateController.swift b/Source/UI/CandidateUI/VerticalCandidateController.swift index 7f3acb8d..3c571c90 100644 --- a/Source/UI/CandidateUI/VerticalCandidateController.swift +++ b/Source/UI/CandidateUI/VerticalCandidateController.swift @@ -1,417 +1,467 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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 -fileprivate class VerticalCandidateView: NSView { - var highlightedIndex: UInt = 0 - var action: Selector? - weak var target: AnyObject? +private class VerticalCandidateView: NSView { + var highlightedIndex: UInt = 0 + var action: Selector? + weak var target: AnyObject? - private var keyLabels: [String] = [] - private var displayedCandidates: [String] = [] - private var dispCandidatesWithLabels: [String] = [] - private var keyLabelHeight: CGFloat = 0 - private var keyLabelWidth: CGFloat = 0 - private var candidateTextHeight: CGFloat = 0 - private var cellPadding: CGFloat = 0 - private var keyLabelAttrDict: [NSAttributedString.Key: AnyObject] = [:] - private var candidateAttrDict: [NSAttributedString.Key: AnyObject] = [:] - private var candidateWithLabelAttrDict: [NSAttributedString.Key: AnyObject] = [:] - private var windowWidth: CGFloat = 0 - private var elementWidths: [CGFloat] = [] - private var elementHeights: [CGFloat] = [] - private var trackingHighlightedIndex: UInt = UInt.max + private var keyLabels: [String] = [] + private var displayedCandidates: [String] = [] + private var dispCandidatesWithLabels: [String] = [] + private var keyLabelHeight: CGFloat = 0 + private var keyLabelWidth: CGFloat = 0 + private var candidateTextHeight: CGFloat = 0 + private var cellPadding: CGFloat = 0 + private var keyLabelAttrDict: [NSAttributedString.Key: AnyObject] = [:] + private var candidateAttrDict: [NSAttributedString.Key: AnyObject] = [:] + private var candidateWithLabelAttrDict: [NSAttributedString.Key: AnyObject] = [:] + private var windowWidth: CGFloat = 0 + private var elementWidths: [CGFloat] = [] + private var elementHeights: [CGFloat] = [] + private var trackingHighlightedIndex: UInt = UInt.max - override var isFlipped: Bool { - true - } + override var isFlipped: Bool { + true + } - var sizeForView: NSSize { - var result = NSSize.zero + var sizeForView: NSSize { + var result = NSSize.zero - if !elementWidths.isEmpty { - result.width = windowWidth - result.height = elementHeights.reduce(0, +) - } - return result - } + if !elementWidths.isEmpty { + result.width = windowWidth + result.height = elementHeights.reduce(0, +) + } + return result + } - @objc(setKeyLabels:displayedCandidates:) - func set(keyLabels labels: [String], displayedCandidates candidates: [String]) { - let count = min(labels.count, candidates.count) - keyLabels = Array(labels[0.. UInt? { - let location = convert(event.locationInWindow, to: nil) - if !NSPointInRect(location, self.bounds) { - return nil - } - var accuHeight: CGFloat = 0.0 - for index in 0..= accuHeight && location.y <= accuHeight + currentHeight { - return UInt(index) - } - accuHeight += currentHeight - } - return nil + var activeCandidateIndexAttr = keyLabelAttrDict + var activeCandidateAttr = candidateAttrDict + if index == highlightedIndex { + let colorBlendAmount: CGFloat = IME.isDarkMode() ? 0.25 : 0 + // The background color of the highlightened candidate + switch ctlInputMethod.currentKeyHandler.inputMode { + case InputMode.imeModeCHS: + NSColor.systemRed.blended( + withFraction: colorBlendAmount, + of: NSColor.controlBackgroundColor)! + .setFill() + case InputMode.imeModeCHT: + NSColor.systemBlue.blended( + withFraction: colorBlendAmount, + of: NSColor.controlBackgroundColor)! + .setFill() + default: + NSColor.alternateSelectedControlColor.setFill() + } + // Highlightened index text color + activeCandidateIndexAttr[.foregroundColor] = NSColor.selectedMenuItemTextColor + .withAlphaComponent(0.84) + // Highlightened phrase text color + activeCandidateAttr[.foregroundColor] = NSColor.selectedMenuItemTextColor + } else { + NSColor.controlBackgroundColor.setFill() + } + switch ctlInputMethod.currentKeyHandler.inputMode { + case InputMode.imeModeCHS: + if #available(macOS 12.0, *) { + activeCandidateAttr[.languageIdentifier] = "zh-Hans" as AnyObject + } + case InputMode.imeModeCHT: + if #available(macOS 12.0, *) { + activeCandidateAttr[.languageIdentifier] = "zh-Hant" as AnyObject + } + default: + break + } + NSBezierPath.fill(rctCandidateArea) + (keyLabels[index] as NSString).draw( + in: rctLabel, withAttributes: activeCandidateIndexAttr) + (displayedCandidates[index] as NSString).draw( + in: rctCandidatePhrase, withAttributes: activeCandidateAttr) + accuHeight += currentHeight + } + } - } + private func findHitIndex(event: NSEvent) -> UInt? { + let location = convert(event.locationInWindow, to: nil) + if !NSPointInRect(location, self.bounds) { + return nil + } + var accuHeight: CGFloat = 0.0 + for index in 0..= accuHeight && location.y <= accuHeight + currentHeight { + return UInt(index) + } + accuHeight += currentHeight + } + return nil - override func mouseDown(with event: NSEvent) { - guard let newIndex = findHitIndex(event: event) else { - return - } - var triggerAction = false - if newIndex == highlightedIndex { - triggerAction = true - } else { - highlightedIndex = trackingHighlightedIndex - } + } - trackingHighlightedIndex = 0 - self.setNeedsDisplay(self.bounds) - if triggerAction { - if let target = target as? NSObject, let action = action { - target.perform(action, with: self) - } - } - } + override func mouseUp(with event: NSEvent) { + trackingHighlightedIndex = highlightedIndex + guard let newIndex = findHitIndex(event: event) else { + return + } + highlightedIndex = newIndex + self.setNeedsDisplay(self.bounds) + } + + override func mouseDown(with event: NSEvent) { + guard let newIndex = findHitIndex(event: event) else { + return + } + var triggerAction = false + if newIndex == highlightedIndex { + triggerAction = true + } else { + highlightedIndex = trackingHighlightedIndex + } + + trackingHighlightedIndex = 0 + self.setNeedsDisplay(self.bounds) + if triggerAction { + if let target = target as? NSObject, let action = action { + target.perform(action, with: self) + } + } + } } @objc(VTVerticalCandidateController) public class VerticalCandidateController: CandidateController { - private var candidateView: VerticalCandidateView - private var prevPageButton: NSButton - private var nextPageButton: NSButton - private var currentPage: UInt = 0 + private var candidateView: VerticalCandidateView + private var prevPageButton: NSButton + private var nextPageButton: NSButton + private var currentPage: UInt = 0 - public init() { - var contentRect = NSRect(x: 128.0, y: 128.0, width: 0.0, height: 0.0) - let styleMask: NSWindow.StyleMask = [.nonactivatingPanel] - let panel = NSPanel(contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false) - panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel) + 1) - panel.hasShadow = true - panel.isOpaque = false - panel.backgroundColor = NSColor.clear - - contentRect.origin = NSPoint.zero - candidateView = VerticalCandidateView(frame: contentRect) + public init() { + var contentRect = NSRect(x: 128.0, y: 128.0, width: 0.0, height: 0.0) + let styleMask: NSWindow.StyleMask = [.nonactivatingPanel] + let panel = NSPanel( + contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false) + panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel) + 1) + panel.hasShadow = true + panel.isOpaque = false + panel.backgroundColor = NSColor.clear - candidateView.wantsLayer = true - candidateView.layer?.borderColor = NSColor.selectedMenuItemTextColor.withAlphaComponent(0.30).cgColor - candidateView.layer?.borderWidth = 1.0 - if #available(macOS 10.13, *) { - candidateView.layer?.cornerRadius = 6.0 - } + contentRect.origin = NSPoint.zero + candidateView = VerticalCandidateView(frame: contentRect) - panel.contentView?.addSubview(candidateView) + candidateView.wantsLayer = true + candidateView.layer?.borderColor = + NSColor.selectedMenuItemTextColor.withAlphaComponent(0.30).cgColor + candidateView.layer?.borderWidth = 1.0 + if #available(macOS 10.13, *) { + candidateView.layer?.cornerRadius = 6.0 + } - contentRect.size = NSSize(width: 20.0, height: 10.0) // Reduce the button width - let buttonAttribute: [NSAttributedString.Key : Any] = [.font : NSFont.systemFont(ofSize: 9.0)] + panel.contentView?.addSubview(candidateView) - nextPageButton = NSButton(frame: contentRect) - NSColor.controlBackgroundColor.setFill() - NSBezierPath.fill(nextPageButton.bounds) - nextPageButton.wantsLayer = true - nextPageButton.layer?.masksToBounds = true - nextPageButton.layer?.borderColor = NSColor.clear.cgColor - nextPageButton.layer?.borderWidth = 0.0 - nextPageButton.setButtonType(.momentaryLight) - nextPageButton.bezelStyle = .disclosure - nextPageButton.userInterfaceLayoutDirection = .leftToRight - nextPageButton.attributedTitle = NSMutableAttributedString(string: " ", attributes: buttonAttribute) // Next Page Arrow - prevPageButton = NSButton(frame: contentRect) - NSColor.controlBackgroundColor.setFill() - NSBezierPath.fill(prevPageButton.bounds) - prevPageButton.wantsLayer = true - prevPageButton.layer?.masksToBounds = true - prevPageButton.layer?.borderColor = NSColor.clear.cgColor - prevPageButton.layer?.borderWidth = 0.0 - prevPageButton.setButtonType(.momentaryLight) - prevPageButton.bezelStyle = .disclosure - prevPageButton.userInterfaceLayoutDirection = .rightToLeft - prevPageButton.attributedTitle = NSMutableAttributedString(string: " ", attributes: buttonAttribute) // Previous Page Arrow - panel.contentView?.addSubview(nextPageButton) - panel.contentView?.addSubview(prevPageButton) + contentRect.size = NSSize(width: 20.0, height: 10.0) // Reduce the button width + let buttonAttribute: [NSAttributedString.Key: Any] = [.font: NSFont.systemFont(ofSize: 9.0)] - super.init(window: panel) + nextPageButton = NSButton(frame: contentRect) + NSColor.controlBackgroundColor.setFill() + NSBezierPath.fill(nextPageButton.bounds) + nextPageButton.wantsLayer = true + nextPageButton.layer?.masksToBounds = true + nextPageButton.layer?.borderColor = NSColor.clear.cgColor + nextPageButton.layer?.borderWidth = 0.0 + nextPageButton.setButtonType(.momentaryLight) + nextPageButton.bezelStyle = .disclosure + nextPageButton.userInterfaceLayoutDirection = .leftToRight + nextPageButton.attributedTitle = NSMutableAttributedString( + string: " ", attributes: buttonAttribute) // Next Page Arrow + prevPageButton = NSButton(frame: contentRect) + NSColor.controlBackgroundColor.setFill() + NSBezierPath.fill(prevPageButton.bounds) + prevPageButton.wantsLayer = true + prevPageButton.layer?.masksToBounds = true + prevPageButton.layer?.borderColor = NSColor.clear.cgColor + prevPageButton.layer?.borderWidth = 0.0 + prevPageButton.setButtonType(.momentaryLight) + prevPageButton.bezelStyle = .disclosure + prevPageButton.userInterfaceLayoutDirection = .rightToLeft + prevPageButton.attributedTitle = NSMutableAttributedString( + string: " ", attributes: buttonAttribute) // Previous Page Arrow + panel.contentView?.addSubview(nextPageButton) + panel.contentView?.addSubview(prevPageButton) - candidateView.target = self - candidateView.action = #selector(candidateViewMouseDidClick(_:)) + super.init(window: panel) - nextPageButton.target = self - nextPageButton.action = #selector(pageButtonAction(_:)) + candidateView.target = self + candidateView.action = #selector(candidateViewMouseDidClick(_:)) - prevPageButton.target = self - prevPageButton.action = #selector(pageButtonAction(_:)) - } + nextPageButton.target = self + nextPageButton.action = #selector(pageButtonAction(_:)) - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } + prevPageButton.target = self + prevPageButton.action = #selector(pageButtonAction(_:)) + } - public override func reloadData() { - candidateView.highlightedIndex = 0 - currentPage = 0 - layoutCandidateView() - } + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } - public override func showNextPage() -> Bool { - guard delegate != nil else {return false} - if pageCount == 1 {return highlightNextCandidate()} - currentPage = (currentPage + 1 >= pageCount) ? 0 : currentPage + 1 - candidateView.highlightedIndex = 0 - layoutCandidateView() - return true - } + public override func reloadData() { + candidateView.highlightedIndex = 0 + currentPage = 0 + layoutCandidateView() + } - public override func showPreviousPage() -> Bool { - guard delegate != nil else {return false} - if pageCount == 1 {return highlightPreviousCandidate()} - currentPage = (currentPage == 0) ? pageCount - 1 : currentPage - 1 - candidateView.highlightedIndex = 0 - layoutCandidateView() - return true - } + public override func showNextPage() -> Bool { + guard delegate != nil else { return false } + if pageCount == 1 { return highlightNextCandidate() } + currentPage = (currentPage + 1 >= pageCount) ? 0 : currentPage + 1 + candidateView.highlightedIndex = 0 + layoutCandidateView() + return true + } - public override func highlightNextCandidate() -> Bool { - guard let delegate = delegate else {return false} - selectedCandidateIndex = (selectedCandidateIndex + 1 >= delegate.candidateCountForController(self)) ? 0 : selectedCandidateIndex + 1 - return true - } + public override func showPreviousPage() -> Bool { + guard delegate != nil else { return false } + if pageCount == 1 { return highlightPreviousCandidate() } + currentPage = (currentPage == 0) ? pageCount - 1 : currentPage - 1 + candidateView.highlightedIndex = 0 + layoutCandidateView() + return true + } - public override func highlightPreviousCandidate() -> Bool { - guard let delegate = delegate else {return false} - selectedCandidateIndex = (selectedCandidateIndex == 0) ? delegate.candidateCountForController(self) - 1 : selectedCandidateIndex - 1 - return true - } + public override func highlightNextCandidate() -> Bool { + guard let delegate = delegate else { return false } + selectedCandidateIndex = + (selectedCandidateIndex + 1 >= delegate.candidateCountForController(self)) + ? 0 : selectedCandidateIndex + 1 + return true + } - public override func candidateIndexAtKeyLabelIndex(_ index: UInt) -> UInt { - guard let delegate = delegate else { - return UInt.max - } + public override func highlightPreviousCandidate() -> Bool { + guard let delegate = delegate else { return false } + selectedCandidateIndex = + (selectedCandidateIndex == 0) + ? delegate.candidateCountForController(self) - 1 : selectedCandidateIndex - 1 + return true + } - let result = currentPage * UInt(keyLabels.count) + index - return result < delegate.candidateCountForController(self) ? result : UInt.max - } + public override func candidateIndexAtKeyLabelIndex(_ index: UInt) -> UInt { + guard let delegate = delegate else { + return UInt.max + } - public override var selectedCandidateIndex: UInt { - get { - currentPage * UInt(keyLabels.count) + candidateView.highlightedIndex - } - set { - guard let delegate = delegate else { - return - } - let keyLabelCount = UInt(keyLabels.count) - if newValue < delegate.candidateCountForController(self) { - currentPage = newValue / keyLabelCount - candidateView.highlightedIndex = newValue % keyLabelCount - layoutCandidateView() - } - } - } + let result = currentPage * UInt(keyLabels.count) + index + return result < delegate.candidateCountForController(self) ? result : UInt.max + } + + public override var selectedCandidateIndex: UInt { + get { + currentPage * UInt(keyLabels.count) + candidateView.highlightedIndex + } + set { + guard let delegate = delegate else { + return + } + let keyLabelCount = UInt(keyLabels.count) + if newValue < delegate.candidateCountForController(self) { + currentPage = newValue / keyLabelCount + candidateView.highlightedIndex = newValue % keyLabelCount + layoutCandidateView() + } + } + } } extension VerticalCandidateController { - private var pageCount: UInt { - guard let delegate = delegate else { - return 0 - } - let totalCount = delegate.candidateCountForController(self) - let keyLabelCount = UInt(keyLabels.count) - return totalCount / keyLabelCount + ((totalCount % keyLabelCount) != 0 ? 1 : 0) - } + private var pageCount: UInt { + guard let delegate = delegate else { + return 0 + } + let totalCount = delegate.candidateCountForController(self) + let keyLabelCount = UInt(keyLabels.count) + return totalCount / keyLabelCount + ((totalCount % keyLabelCount) != 0 ? 1 : 0) + } - private func layoutCandidateView() { - guard let delegate = delegate else { - return - } + private func layoutCandidateView() { + guard let delegate = delegate else { + return + } - candidateView.set(keyLabelFont: keyLabelFont, candidateFont: candidateFont) - var candidates = [String]() - let count = delegate.candidateCountForController(self) - let keyLabelCount = UInt(keyLabels.count) + candidateView.set(keyLabelFont: keyLabelFont, candidateFont: candidateFont) + var candidates = [String]() + let count = delegate.candidateCountForController(self) + let keyLabelCount = UInt(keyLabels.count) - let begin = currentPage * keyLabelCount - for index in begin.. 1 && mgrPrefs.showPageButtonsInCandidateWindow { - var buttonRect = nextPageButton.frame - let spacing:CGFloat = 0.0 + if pageCount > 1 && mgrPrefs.showPageButtonsInCandidateWindow { + var buttonRect = nextPageButton.frame + let spacing: CGFloat = 0.0 - // buttonRect.size.height = floor(candidateTextHeight + cellPadding / 2) + // buttonRect.size.height = floor(candidateTextHeight + cellPadding / 2) - let buttonOriginY = (newSize.height - (buttonRect.size.height * 2.0 + spacing)) // / 2.0 - buttonRect.origin = NSPoint(x: newSize.width, y: buttonOriginY) - nextPageButton.frame = buttonRect + let buttonOriginY = (newSize.height - (buttonRect.size.height * 2.0 + spacing)) // / 2.0 + buttonRect.origin = NSPoint(x: newSize.width, y: buttonOriginY) + nextPageButton.frame = buttonRect - buttonRect.origin = NSPoint(x: newSize.width, y: buttonOriginY + buttonRect.size.height + spacing) - prevPageButton.frame = buttonRect + buttonRect.origin = NSPoint( + x: newSize.width, y: buttonOriginY + buttonRect.size.height + spacing) + prevPageButton.frame = buttonRect - newSize.width += 20 - nextPageButton.isHidden = false - prevPageButton.isHidden = false - } else { - nextPageButton.isHidden = true - prevPageButton.isHidden = true - } + newSize.width += 20 + nextPageButton.isHidden = false + prevPageButton.isHidden = false + } else { + nextPageButton.isHidden = true + prevPageButton.isHidden = true + } - frameRect = window?.frame ?? NSRect.zero + frameRect = window?.frame ?? NSRect.zero - let topLeftPoint = NSMakePoint(frameRect.origin.x, frameRect.origin.y + frameRect.size.height) - frameRect.size = newSize - frameRect.origin = NSMakePoint(topLeftPoint.x, topLeftPoint.y - frameRect.size.height) - self.window?.setFrame(frameRect, display: false) - candidateView.setNeedsDisplay(candidateView.bounds) - } + let topLeftPoint = NSMakePoint( + frameRect.origin.x, frameRect.origin.y + frameRect.size.height) + frameRect.size = newSize + frameRect.origin = NSMakePoint(topLeftPoint.x, topLeftPoint.y - frameRect.size.height) + self.window?.setFrame(frameRect, display: false) + candidateView.setNeedsDisplay(candidateView.bounds) + } - @objc fileprivate func pageButtonAction(_ sender: Any) { - guard let sender = sender as? NSButton else { - return - } - if sender == nextPageButton { - _ = showNextPage() - } else if sender == prevPageButton { - _ = showPreviousPage() - } - } + @objc fileprivate func pageButtonAction(_ sender: Any) { + guard let sender = sender as? NSButton else { + return + } + if sender == nextPageButton { + _ = showNextPage() + } else if sender == prevPageButton { + _ = showPreviousPage() + } + } - @objc fileprivate func candidateViewMouseDidClick(_ sender: Any) { - delegate?.candidateController(self, didSelectCandidateAtIndex: selectedCandidateIndex) - } + @objc fileprivate func candidateViewMouseDidClick(_ sender: Any) { + delegate?.candidateController(self, didSelectCandidateAtIndex: selectedCandidateIndex) + } } diff --git a/Source/UI/NotifierUI/NotifierController.swift b/Source/UI/NotifierUI/NotifierController.swift index d4f83084..54bcbfff 100644 --- a/Source/UI/NotifierUI/NotifierController.swift +++ b/Source/UI/NotifierUI/NotifierController.swift @@ -1,203 +1,216 @@ // Copyright (c) 2021 and onwards Weizhong Yang (MIT License). // All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ import Cocoa private protocol NotifierWindowDelegate: AnyObject { - func windowDidBecomeClicked(_ window: NotifierWindow) + func windowDidBecomeClicked(_ window: NotifierWindow) } private class NotifierWindow: NSWindow { - weak var clickDelegate: NotifierWindowDelegate? + weak var clickDelegate: NotifierWindowDelegate? - override func mouseDown(with event: NSEvent) { - clickDelegate?.windowDidBecomeClicked(self) - } + override func mouseDown(with event: NSEvent) { + clickDelegate?.windowDidBecomeClicked(self) + } } private let kWindowWidth: CGFloat = 213.0 private let kWindowHeight: CGFloat = 60.0 public class NotifierController: NSWindowController, NotifierWindowDelegate { - private var messageTextField: NSTextField + private var messageTextField: NSTextField - private var message: String = "" { - didSet { - let paraStyle = NSMutableParagraphStyle() - paraStyle.setParagraphStyle(NSParagraphStyle.default) - paraStyle.alignment = .center - let attr: [NSAttributedString.Key: AnyObject] = [ - .foregroundColor: foregroundColor, - .font: NSFont.boldSystemFont(ofSize: NSFont.systemFontSize(for: .regular)), - .paragraphStyle: paraStyle - ] - let attrString = NSAttributedString(string: message, attributes: attr) - messageTextField.attributedStringValue = attrString - let width = window?.frame.width ?? kWindowWidth - let rect = attrString.boundingRect(with: NSSize(width: width, height: 1600), options: .usesLineFragmentOrigin) - let height = rect.height - let x = messageTextField.frame.origin.x - let y = ((window?.frame.height ?? kWindowHeight) - height) / 2 - let newFrame = NSRect(x: x, y: y, width: width, height: height) - messageTextField.frame = newFrame - } - } - private var shouldStay: Bool = false - private var backgroundColor: NSColor = .textBackgroundColor { - didSet { - self.window?.backgroundColor = backgroundColor - } - } - private var foregroundColor: NSColor = .controlTextColor { - didSet { - self.messageTextField.textColor = foregroundColor - } - } - private var waitTimer: Timer? - private var fadeTimer: Timer? + private var message: String = "" { + didSet { + let paraStyle = NSMutableParagraphStyle() + paraStyle.setParagraphStyle(NSParagraphStyle.default) + paraStyle.alignment = .center + let attr: [NSAttributedString.Key: AnyObject] = [ + .foregroundColor: foregroundColor, + .font: NSFont.boldSystemFont(ofSize: NSFont.systemFontSize(for: .regular)), + .paragraphStyle: paraStyle, + ] + let attrString = NSAttributedString(string: message, attributes: attr) + messageTextField.attributedStringValue = attrString + let width = window?.frame.width ?? kWindowWidth + let rect = attrString.boundingRect( + with: NSSize(width: width, height: 1600), options: .usesLineFragmentOrigin) + let height = rect.height + let x = messageTextField.frame.origin.x + let y = ((window?.frame.height ?? kWindowHeight) - height) / 2 + let newFrame = NSRect(x: x, y: y, width: width, height: height) + messageTextField.frame = newFrame + } + } + private var shouldStay: Bool = false + private var backgroundColor: NSColor = .textBackgroundColor { + didSet { + self.window?.backgroundColor = backgroundColor + } + } + private var foregroundColor: NSColor = .controlTextColor { + didSet { + self.messageTextField.textColor = foregroundColor + } + } + private var waitTimer: Timer? + private var fadeTimer: Timer? - private static var instanceCount = 0 - private static var lastLocation = NSPoint.zero + private static var instanceCount = 0 + private static var lastLocation = NSPoint.zero - @objc public static func notify(message: String, stay: Bool = false) { - let controller = NotifierController() - controller.message = message - controller.shouldStay = stay - controller.show() - } + @objc public static func notify(message: String, stay: Bool = false) { + let controller = NotifierController() + controller.message = message + controller.shouldStay = stay + controller.show() + } - private static func increaseInstanceCount() { - instanceCount += 1 - } + private static func increaseInstanceCount() { + instanceCount += 1 + } - private static func decreaseInstanceCount() { - instanceCount -= 1 - if instanceCount < 0 { - instanceCount = 0 - } - } + private static func decreaseInstanceCount() { + instanceCount -= 1 + if instanceCount < 0 { + instanceCount = 0 + } + } - private init() { - let screenRect = NSScreen.main?.visibleFrame ?? NSRect.zero - let contentRect = NSRect(x: 0, y: 0, width: kWindowWidth, height: kWindowHeight) - var windowRect = contentRect - windowRect.origin.x = screenRect.maxX - windowRect.width - 10 - windowRect.origin.y = screenRect.maxY - windowRect.height - 10 - let styleMask: NSWindow.StyleMask = [.fullSizeContentView, .titled] + private init() { + let screenRect = NSScreen.main?.visibleFrame ?? NSRect.zero + let contentRect = NSRect(x: 0, y: 0, width: kWindowWidth, height: kWindowHeight) + var windowRect = contentRect + windowRect.origin.x = screenRect.maxX - windowRect.width - 10 + windowRect.origin.y = screenRect.maxY - windowRect.height - 10 + let styleMask: NSWindow.StyleMask = [.fullSizeContentView, .titled] - let transparentVisualEffect = NSVisualEffectView() - transparentVisualEffect.blendingMode = .behindWindow - transparentVisualEffect.state = .active + let transparentVisualEffect = NSVisualEffectView() + transparentVisualEffect.blendingMode = .behindWindow + transparentVisualEffect.state = .active - let panel = NotifierWindow(contentRect: windowRect, styleMask: styleMask, backing: .buffered, defer: false) - panel.contentView = transparentVisualEffect - panel.isMovableByWindowBackground = true - panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel)) - panel.hasShadow = true - panel.backgroundColor = backgroundColor - panel.title = "" - panel.titlebarAppearsTransparent = true - panel.titleVisibility = .hidden - panel.showsToolbarButton = false - panel.standardWindowButton(NSWindow.ButtonType.fullScreenButton)?.isHidden = true - panel.standardWindowButton(NSWindow.ButtonType.miniaturizeButton)?.isHidden = true - panel.standardWindowButton(NSWindow.ButtonType.closeButton)?.isHidden = true - panel.standardWindowButton(NSWindow.ButtonType.zoomButton)?.isHidden = true + let panel = NotifierWindow( + contentRect: windowRect, styleMask: styleMask, backing: .buffered, defer: false) + panel.contentView = transparentVisualEffect + panel.isMovableByWindowBackground = true + panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel)) + panel.hasShadow = true + panel.backgroundColor = backgroundColor + panel.title = "" + panel.titlebarAppearsTransparent = true + panel.titleVisibility = .hidden + panel.showsToolbarButton = false + panel.standardWindowButton(NSWindow.ButtonType.fullScreenButton)?.isHidden = true + panel.standardWindowButton(NSWindow.ButtonType.miniaturizeButton)?.isHidden = true + panel.standardWindowButton(NSWindow.ButtonType.closeButton)?.isHidden = true + panel.standardWindowButton(NSWindow.ButtonType.zoomButton)?.isHidden = true - messageTextField = NSTextField() - messageTextField.frame = contentRect - messageTextField.isEditable = false - messageTextField.isSelectable = false - messageTextField.isBezeled = false - messageTextField.textColor = foregroundColor - messageTextField.drawsBackground = false - messageTextField.font = .boldSystemFont(ofSize: NSFont.systemFontSize(for: .regular)) - panel.contentView?.addSubview(messageTextField) + messageTextField = NSTextField() + messageTextField.frame = contentRect + messageTextField.isEditable = false + messageTextField.isSelectable = false + messageTextField.isBezeled = false + messageTextField.textColor = foregroundColor + messageTextField.drawsBackground = false + messageTextField.font = .boldSystemFont(ofSize: NSFont.systemFontSize(for: .regular)) + panel.contentView?.addSubview(messageTextField) - super.init(window: panel) + super.init(window: panel) - panel.clickDelegate = self - } + panel.clickDelegate = self + } - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } - private func show() { - func setStartLocation() { - if NotifierController.instanceCount == 0 { - return - } - let lastLocation = NotifierController.lastLocation - let screenRect = NSScreen.main?.visibleFrame ?? NSRect.zero - var windowRect = self.window?.frame ?? NSRect.zero - windowRect.origin.x = lastLocation.x - windowRect.origin.y = lastLocation.y - 10 - windowRect.height + private func show() { + func setStartLocation() { + if NotifierController.instanceCount == 0 { + return + } + let lastLocation = NotifierController.lastLocation + let screenRect = NSScreen.main?.visibleFrame ?? NSRect.zero + var windowRect = self.window?.frame ?? NSRect.zero + windowRect.origin.x = lastLocation.x + windowRect.origin.y = lastLocation.y - 10 - windowRect.height - if windowRect.origin.y < screenRect.minY { - return - } + if windowRect.origin.y < screenRect.minY { + return + } - self.window?.setFrame(windowRect, display: true) - } + self.window?.setFrame(windowRect, display: true) + } - func moveIn() { - let afterRect = self.window?.frame ?? NSRect.zero - NotifierController.lastLocation = afterRect.origin - var beforeRect = afterRect - beforeRect.origin.y += 10 - window?.setFrame(beforeRect, display: true) - window?.orderFront(self) - window?.setFrame(afterRect, display: true, animate: true) - } + func moveIn() { + let afterRect = self.window?.frame ?? NSRect.zero + NotifierController.lastLocation = afterRect.origin + var beforeRect = afterRect + beforeRect.origin.y += 10 + window?.setFrame(beforeRect, display: true) + window?.orderFront(self) + window?.setFrame(afterRect, display: true, animate: true) + } - setStartLocation() - moveIn() - NotifierController.increaseInstanceCount() - waitTimer = Timer.scheduledTimer(timeInterval: shouldStay ? 5 : 1, target: self, selector: #selector(fadeOut), userInfo: nil, repeats: false) - } + setStartLocation() + moveIn() + NotifierController.increaseInstanceCount() + waitTimer = Timer.scheduledTimer( + timeInterval: shouldStay ? 5 : 1, target: self, selector: #selector(fadeOut), + userInfo: nil, + repeats: false) + } - @objc private func doFadeOut(_ timer: Timer) { - let opacity = self.window?.alphaValue ?? 0 - if opacity <= 0 { - self.close() - } else { - self.window?.alphaValue = opacity - 0.2 - } - } + @objc private func doFadeOut(_ timer: Timer) { + let opacity = self.window?.alphaValue ?? 0 + if opacity <= 0 { + self.close() + } else { + self.window?.alphaValue = opacity - 0.2 + } + } - @objc private func fadeOut() { - waitTimer?.invalidate() - waitTimer = nil - NotifierController.decreaseInstanceCount() - fadeTimer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(doFadeOut(_:)), userInfo: nil, repeats: true) - } + @objc private func fadeOut() { + waitTimer?.invalidate() + waitTimer = nil + NotifierController.decreaseInstanceCount() + fadeTimer = Timer.scheduledTimer( + timeInterval: 0.01, target: self, selector: #selector(doFadeOut(_:)), userInfo: nil, + repeats: true) + } - public override func close() { - waitTimer?.invalidate() - waitTimer = nil - fadeTimer?.invalidate() - fadeTimer = nil - super.close() - } + public override func close() { + waitTimer?.invalidate() + waitTimer = nil + fadeTimer?.invalidate() + fadeTimer = nil + super.close() + } - fileprivate func windowDidBecomeClicked(_ window: NotifierWindow) { - self.fadeOut() - } + fileprivate func windowDidBecomeClicked(_ window: NotifierWindow) { + self.fadeOut() + } } diff --git a/Source/UI/TooltipUI/TooltipController.swift b/Source/UI/TooltipUI/TooltipController.swift index ecb41aff..7a2f0c81 100644 --- a/Source/UI/TooltipUI/TooltipController.swift +++ b/Source/UI/TooltipUI/TooltipController.swift @@ -1,122 +1,129 @@ // Copyright (c) 2021 and onwards Weizhong Yang (MIT License). // All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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 public class TooltipController: NSWindowController { - static var backgroundColor = NSColor.windowBackgroundColor - static var textColor = NSColor.windowBackgroundColor - private var messageTextField: NSTextField - private var tooltip: String = "" { - didSet { - messageTextField.stringValue = tooltip - adjustSize() - } - } + static var backgroundColor = NSColor.windowBackgroundColor + static var textColor = NSColor.windowBackgroundColor + private var messageTextField: NSTextField + private var tooltip: String = "" { + didSet { + messageTextField.stringValue = tooltip + adjustSize() + } + } - public init() { - let contentRect = NSRect(x: 128.0, y: 128.0, width: 300.0, height: 20.0) - let styleMask: NSWindow.StyleMask = [.borderless, .nonactivatingPanel] - let panel = NSPanel(contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false) - panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel) + 1) - panel.hasShadow = true + public init() { + let contentRect = NSRect(x: 128.0, y: 128.0, width: 300.0, height: 20.0) + let styleMask: NSWindow.StyleMask = [.borderless, .nonactivatingPanel] + let panel = NSPanel( + contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false) + panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel) + 1) + panel.hasShadow = true - messageTextField = NSTextField() - messageTextField.isEditable = false - messageTextField.isSelectable = false - messageTextField.isBezeled = false - messageTextField.textColor = TooltipController.textColor - messageTextField.drawsBackground = true - messageTextField.backgroundColor = TooltipController.backgroundColor - messageTextField.font = .systemFont(ofSize: NSFont.systemFontSize(for: .small)) - panel.contentView?.addSubview(messageTextField) + messageTextField = NSTextField() + messageTextField.isEditable = false + messageTextField.isSelectable = false + messageTextField.isBezeled = false + messageTextField.textColor = TooltipController.textColor + messageTextField.drawsBackground = true + messageTextField.backgroundColor = TooltipController.backgroundColor + messageTextField.font = .systemFont(ofSize: NSFont.systemFontSize(for: .small)) + panel.contentView?.addSubview(messageTextField) - super.init(window: panel) - } + super.init(window: panel) + } - public required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } + public required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } - @objc(showTooltip:atPoint:) - public func show(tooltip: String, at point: NSPoint) { - messageTextField.textColor = TooltipController.textColor - messageTextField.backgroundColor = TooltipController.backgroundColor - self.tooltip = tooltip - window?.orderFront(nil) - set(windowLocation: point) - } + @objc(showTooltip:atPoint:) + public func show(tooltip: String, at point: NSPoint) { + messageTextField.textColor = TooltipController.textColor + messageTextField.backgroundColor = TooltipController.backgroundColor + self.tooltip = tooltip + window?.orderFront(nil) + set(windowLocation: point) + } - @objc - public func hide() { - window?.orderOut(nil) - } + @objc + public func hide() { + window?.orderOut(nil) + } - private func set(windowLocation windowTopLeftPoint: NSPoint) { + private func set(windowLocation windowTopLeftPoint: NSPoint) { - var adjustedPoint = windowTopLeftPoint - adjustedPoint.y -= 5 + var adjustedPoint = windowTopLeftPoint + adjustedPoint.y -= 5 - var screenFrame = NSScreen.main?.visibleFrame ?? NSRect.zero - for screen in NSScreen.screens { - let frame = screen.visibleFrame - if windowTopLeftPoint.x >= frame.minX && - windowTopLeftPoint.x <= frame.maxX && - windowTopLeftPoint.y >= frame.minY && - windowTopLeftPoint.y <= frame.maxY { - screenFrame = frame - break - } - } + var screenFrame = NSScreen.main?.visibleFrame ?? NSRect.zero + for screen in NSScreen.screens { + let frame = screen.visibleFrame + if windowTopLeftPoint.x >= frame.minX && windowTopLeftPoint.x <= frame.maxX + && windowTopLeftPoint.y >= frame.minY && windowTopLeftPoint.y <= frame.maxY + { + screenFrame = frame + break + } + } - let windowSize = window?.frame.size ?? NSSize.zero + let windowSize = window?.frame.size ?? NSSize.zero - // bottom beneath the screen? - if adjustedPoint.y - windowSize.height < screenFrame.minY { - adjustedPoint.y = screenFrame.minY + windowSize.height - } + // bottom beneath the screen? + if adjustedPoint.y - windowSize.height < screenFrame.minY { + adjustedPoint.y = screenFrame.minY + windowSize.height + } - // top over the screen? - if adjustedPoint.y >= screenFrame.maxY { - adjustedPoint.y = screenFrame.maxY - 1.0 - } + // top over the screen? + if adjustedPoint.y >= screenFrame.maxY { + adjustedPoint.y = screenFrame.maxY - 1.0 + } - // right - if adjustedPoint.x + windowSize.width >= screenFrame.maxX { - adjustedPoint.x = screenFrame.maxX - windowSize.width - } + // right + if adjustedPoint.x + windowSize.width >= screenFrame.maxX { + adjustedPoint.x = screenFrame.maxX - windowSize.width + } - // left - if adjustedPoint.x < screenFrame.minX { - adjustedPoint.x = screenFrame.minX - } + // left + if adjustedPoint.x < screenFrame.minX { + adjustedPoint.x = screenFrame.minX + } - window?.setFrameTopLeftPoint(adjustedPoint) + window?.setFrameTopLeftPoint(adjustedPoint) - } + } - private func adjustSize() { - let attrString = messageTextField.attributedStringValue; - var rect = attrString.boundingRect(with: NSSize(width: 1600.0, height: 1600.0), options: .usesLineFragmentOrigin) - rect.size.width += 10 - messageTextField.frame = rect - window?.setFrame(rect, display: true) - } + private func adjustSize() { + let attrString = messageTextField.attributedStringValue + var rect = attrString.boundingRect( + with: NSSize(width: 1600.0, height: 1600.0), options: .usesLineFragmentOrigin) + rect.size.width += 10 + messageTextField.frame = rect + window?.setFrame(rect, display: true) + } } diff --git a/Source/WindowControllers/ctlAboutWindow.swift b/Source/WindowControllers/ctlAboutWindow.swift index c144e201..e38945b5 100644 --- a/Source/WindowControllers/ctlAboutWindow.swift +++ b/Source/WindowControllers/ctlAboutWindow.swift @@ -1,51 +1,64 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ import Cocoa @objc(AboutWindow) class ctlAboutWindow: NSWindowController { - @IBOutlet weak var appVersionLabel: NSTextField! - @IBOutlet weak var appCopyrightLabel: NSTextField! - @IBOutlet var appEULAContent: NSTextView! + @IBOutlet weak var appVersionLabel: NSTextField! + @IBOutlet weak var appCopyrightLabel: NSTextField! + @IBOutlet var appEULAContent: NSTextView! - override func windowDidLoad() { - super.windowDidLoad() - - window?.standardWindowButton(.closeButton)?.isHidden = true - window?.standardWindowButton(.miniaturizeButton)?.isHidden = true - window?.standardWindowButton(.zoomButton)?.isHidden = true - guard let installingVersion = Bundle.main.infoDictionary?[kCFBundleVersionKey as String] as? String, - let versionString = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String else { - return - } - if let copyrightLabel = Bundle.main.localizedInfoDictionary?["NSHumanReadableCopyright"] as? String { - appCopyrightLabel.stringValue = copyrightLabel - } - if let eulaContent = Bundle.main.localizedInfoDictionary?["CFEULAContent"] as? String { - appEULAContent.string = eulaContent - } - appVersionLabel.stringValue = String(format: "%@ Build %@", versionString, installingVersion) - } + override func windowDidLoad() { + super.windowDidLoad() - @IBAction func btnWiki(_ sender: NSButton) { - if let url = URL(string: "https://gitee.com/vchewing/vChewing-macOS/wikis") { - NSWorkspace.shared.open(url) - } - } + window?.standardWindowButton(.closeButton)?.isHidden = true + window?.standardWindowButton(.miniaturizeButton)?.isHidden = true + window?.standardWindowButton(.zoomButton)?.isHidden = true + guard + let installingVersion = Bundle.main.infoDictionary?[kCFBundleVersionKey as String] + as? String, + let versionString = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String + else { + return + } + if let copyrightLabel = Bundle.main.localizedInfoDictionary?["NSHumanReadableCopyright"] + as? String + { + appCopyrightLabel.stringValue = copyrightLabel + } + if let eulaContent = Bundle.main.localizedInfoDictionary?["CFEULAContent"] as? String { + appEULAContent.string = eulaContent + } + appVersionLabel.stringValue = String( + format: "%@ Build %@", versionString, installingVersion) + } + + @IBAction func btnWiki(_ sender: NSButton) { + if let url = URL(string: "https://gitee.com/vchewing/vChewing-macOS/wikis") { + NSWorkspace.shared.open(url) + } + } } diff --git a/Source/WindowControllers/ctlNonModalAlertWindow.swift b/Source/WindowControllers/ctlNonModalAlertWindow.swift index a9ed42a3..ec38f9a1 100644 --- a/Source/WindowControllers/ctlNonModalAlertWindow.swift +++ b/Source/WindowControllers/ctlNonModalAlertWindow.swift @@ -1,118 +1,130 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ import Cocoa @objc protocol ctlNonModalAlertWindowDelegate: AnyObject { - func ctlNonModalAlertWindowDidConfirm(_ controller: ctlNonModalAlertWindow) - func ctlNonModalAlertWindowDidCancel(_ controller: ctlNonModalAlertWindow) + func ctlNonModalAlertWindowDidConfirm(_ controller: ctlNonModalAlertWindow) + func ctlNonModalAlertWindowDidCancel(_ controller: ctlNonModalAlertWindow) } class ctlNonModalAlertWindow: NSWindowController { - @objc(sharedInstance) - static let shared = ctlNonModalAlertWindow(windowNibName: "frmNonModalAlertWindow") + @objc(sharedInstance) + static let shared = ctlNonModalAlertWindow(windowNibName: "frmNonModalAlertWindow") - @IBOutlet weak var titleTextField: NSTextField! - @IBOutlet weak var contentTextField: NSTextField! - @IBOutlet weak var confirmButton: NSButton! - @IBOutlet weak var cancelButton: NSButton! - weak var delegate: ctlNonModalAlertWindowDelegate? + @IBOutlet weak var titleTextField: NSTextField! + @IBOutlet weak var contentTextField: NSTextField! + @IBOutlet weak var confirmButton: NSButton! + @IBOutlet weak var cancelButton: NSButton! + weak var delegate: ctlNonModalAlertWindowDelegate? - @objc func show(title: String, content: String, confirmButtonTitle: String, cancelButtonTitle: String?, cancelAsDefault: Bool, delegate: ctlNonModalAlertWindowDelegate?) { - if window?.isVisible == true { - self.delegate?.ctlNonModalAlertWindowDidCancel(self) - } + @objc func show( + title: String, content: String, confirmButtonTitle: String, cancelButtonTitle: String?, + cancelAsDefault: Bool, delegate: ctlNonModalAlertWindowDelegate? + ) { + if window?.isVisible == true { + self.delegate?.ctlNonModalAlertWindowDidCancel(self) + } - self.delegate = delegate + self.delegate = delegate - var oldFrame = confirmButton.frame - confirmButton.title = confirmButtonTitle - confirmButton.sizeToFit() + var oldFrame = confirmButton.frame + confirmButton.title = confirmButtonTitle + confirmButton.sizeToFit() - var newFrame = confirmButton.frame - newFrame.size.width = max(90, newFrame.size.width + 10) - newFrame.origin.x += oldFrame.size.width - newFrame.size.width - confirmButton.frame = newFrame + var newFrame = confirmButton.frame + newFrame.size.width = max(90, newFrame.size.width + 10) + newFrame.origin.x += oldFrame.size.width - newFrame.size.width + confirmButton.frame = newFrame - if let cancelButtonTitle = cancelButtonTitle { - cancelButton.title = cancelButtonTitle - cancelButton.sizeToFit() - var adjustFrame = cancelButton.frame - adjustFrame.size.width = max(90, adjustFrame.size.width + 10) - adjustFrame.origin.x = newFrame.origin.x - adjustFrame.size.width - confirmButton.frame = adjustFrame - cancelButton.isHidden = false - } else { - cancelButton.isHidden = true - } + if let cancelButtonTitle = cancelButtonTitle { + cancelButton.title = cancelButtonTitle + cancelButton.sizeToFit() + var adjustFrame = cancelButton.frame + adjustFrame.size.width = max(90, adjustFrame.size.width + 10) + adjustFrame.origin.x = newFrame.origin.x - adjustFrame.size.width + confirmButton.frame = adjustFrame + cancelButton.isHidden = false + } else { + cancelButton.isHidden = true + } - cancelButton.nextKeyView = confirmButton - confirmButton.nextKeyView = cancelButton + cancelButton.nextKeyView = confirmButton + confirmButton.nextKeyView = cancelButton - if cancelButtonTitle != nil { - if cancelAsDefault { - window?.defaultButtonCell = cancelButton.cell as? NSButtonCell - } else { - cancelButton.keyEquivalent = " " - window?.defaultButtonCell = confirmButton.cell as? NSButtonCell - } - } else { - window?.defaultButtonCell = confirmButton.cell as? NSButtonCell - } + if cancelButtonTitle != nil { + if cancelAsDefault { + window?.defaultButtonCell = cancelButton.cell as? NSButtonCell + } else { + cancelButton.keyEquivalent = " " + window?.defaultButtonCell = confirmButton.cell as? NSButtonCell + } + } else { + window?.defaultButtonCell = confirmButton.cell as? NSButtonCell + } - titleTextField.stringValue = title + titleTextField.stringValue = title - oldFrame = contentTextField.frame - contentTextField.stringValue = content + oldFrame = contentTextField.frame + contentTextField.stringValue = content - var infiniteHeightFrame = oldFrame - infiniteHeightFrame.size.width -= 4.0 - infiniteHeightFrame.size.height = 10240 - newFrame = (content as NSString).boundingRect(with: infiniteHeightFrame.size, options: [.usesLineFragmentOrigin], attributes: [.font: contentTextField.font!]) - newFrame.size.width = max(newFrame.size.width, oldFrame.size.width) - newFrame.size.height += 4.0 - newFrame.origin = oldFrame.origin - newFrame.origin.y -= (newFrame.size.height - oldFrame.size.height) - contentTextField.frame = newFrame + var infiniteHeightFrame = oldFrame + infiniteHeightFrame.size.width -= 4.0 + infiniteHeightFrame.size.height = 10240 + newFrame = (content as NSString).boundingRect( + with: infiniteHeightFrame.size, options: [.usesLineFragmentOrigin], + attributes: [.font: contentTextField.font!]) + newFrame.size.width = max(newFrame.size.width, oldFrame.size.width) + newFrame.size.height += 4.0 + newFrame.origin = oldFrame.origin + newFrame.origin.y -= (newFrame.size.height - oldFrame.size.height) + contentTextField.frame = newFrame - var windowFrame = window?.frame ?? NSRect.zero - windowFrame.size.height += (newFrame.size.height - oldFrame.size.height) - window?.level = NSWindow.Level(Int(CGShieldingWindowLevel()) + 1) - window?.setFrame(windowFrame, display: true) - window?.center() - window?.makeKeyAndOrderFront(self) - NSApp.activate(ignoringOtherApps: true) - } + var windowFrame = window?.frame ?? NSRect.zero + windowFrame.size.height += (newFrame.size.height - oldFrame.size.height) + window?.level = NSWindow.Level(Int(CGShieldingWindowLevel()) + 1) + window?.setFrame(windowFrame, display: true) + window?.center() + window?.makeKeyAndOrderFront(self) + NSApp.activate(ignoringOtherApps: true) + } - @IBAction func confirmButtonAction(_ sender: Any) { - delegate?.ctlNonModalAlertWindowDidConfirm(self) - window?.orderOut(self) - } + @IBAction func confirmButtonAction(_ sender: Any) { + delegate?.ctlNonModalAlertWindowDidConfirm(self) + window?.orderOut(self) + } - @IBAction func cancelButtonAction(_ sender: Any) { - cancel(sender) - } + @IBAction func cancelButtonAction(_ sender: Any) { + cancel(sender) + } - func cancel(_ sender: Any) { - delegate?.ctlNonModalAlertWindowDidCancel(self) - delegate = nil - window?.orderOut(self) - } + func cancel(_ sender: Any) { + delegate?.ctlNonModalAlertWindowDidCancel(self) + delegate = nil + window?.orderOut(self) + } } diff --git a/Source/WindowControllers/ctlPrefWindow.swift b/Source/WindowControllers/ctlPrefWindow.swift index 26d271e7..23f86590 100644 --- a/Source/WindowControllers/ctlPrefWindow.swift +++ b/Source/WindowControllers/ctlPrefWindow.swift @@ -1,279 +1,301 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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 Carbon +import Cocoa // Extend the RangeReplaceableCollection to allow it clean duplicated characters. extension RangeReplaceableCollection where Element: Hashable { - var charDeDuplicate: Self { - var set = Set() - return filter{ set.insert($0).inserted } - } + var charDeDuplicate: Self { + var set = Set() + return filter { set.insert($0).inserted } + } } // Please note that the class should be exposed using the same class name // in Objective-C in order to let IMK to see the same class name as // the "InputMethodServerPreferencesWindowControllerClass" in Info.plist. @objc(ctlPrefWindow) class ctlPrefWindow: NSWindowController { - @IBOutlet weak var fontSizePopUpButton: NSPopUpButton! - @IBOutlet weak var uiLanguageButton: NSPopUpButton! - @IBOutlet weak var basisKeyboardLayoutButton: NSPopUpButton! - @IBOutlet weak var selectionKeyComboBox: NSComboBox! - @IBOutlet weak var chkTrad2KangXi: NSButton! - @IBOutlet weak var chkTrad2JISShinjitai: NSButton! - @IBOutlet weak var lblCurrentlySpecifiedUserDataFolder: NSTextFieldCell! - - var currentLanguageSelectItem: NSMenuItem? = nil + @IBOutlet weak var fontSizePopUpButton: NSPopUpButton! + @IBOutlet weak var uiLanguageButton: NSPopUpButton! + @IBOutlet weak var basisKeyboardLayoutButton: NSPopUpButton! + @IBOutlet weak var selectionKeyComboBox: NSComboBox! + @IBOutlet weak var chkTrad2KangXi: NSButton! + @IBOutlet weak var chkTrad2JISShinjitai: NSButton! + @IBOutlet weak var lblCurrentlySpecifiedUserDataFolder: NSTextFieldCell! - override func windowDidLoad() { - super.windowDidLoad() + var currentLanguageSelectItem: NSMenuItem? = nil - lblCurrentlySpecifiedUserDataFolder.placeholderString = mgrLangModel.dataFolderPath(isDefaultFolder: true) + override func windowDidLoad() { + super.windowDidLoad() - let languages = ["auto", "en", "zh-Hans", "zh-Hant", "ja"] - var autoMUISelectItem: NSMenuItem? = nil - var chosenLanguageItem: NSMenuItem? = nil - uiLanguageButton.menu?.removeAllItems() - - let appleLanguages = mgrPrefs.appleLanguages - for language in languages { - let menuItem = NSMenuItem() - menuItem.title = NSLocalizedString(language, comment: "") - menuItem.representedObject = language - - if language == "auto" { - autoMUISelectItem = menuItem - } - - if !appleLanguages.isEmpty { - if appleLanguages[0] == language { - chosenLanguageItem = menuItem - } - } - uiLanguageButton.menu?.addItem(menuItem) - } - - currentLanguageSelectItem = chosenLanguageItem ?? autoMUISelectItem - uiLanguageButton.select(currentLanguageSelectItem) + lblCurrentlySpecifiedUserDataFolder.placeholderString = mgrLangModel.dataFolderPath( + isDefaultFolder: true) - let list = TISCreateInputSourceList(nil, true).takeRetainedValue() as! [TISInputSource] - var usKeyboardLayoutItem: NSMenuItem? = nil - var chosenBaseKeyboardLayoutItem: NSMenuItem? = nil + let languages = ["auto", "en", "zh-Hans", "zh-Hant", "ja"] + var autoMUISelectItem: NSMenuItem? = nil + var chosenLanguageItem: NSMenuItem? = nil + uiLanguageButton.menu?.removeAllItems() - basisKeyboardLayoutButton.menu?.removeAllItems() + let appleLanguages = mgrPrefs.appleLanguages + for language in languages { + let menuItem = NSMenuItem() + menuItem.title = NSLocalizedString(language, comment: "") + menuItem.representedObject = language - let menuItem_AppleZhuyinBopomofo = NSMenuItem() - menuItem_AppleZhuyinBopomofo.title = String(format: NSLocalizedString("Apple Zhuyin Bopomofo", comment: "")) - menuItem_AppleZhuyinBopomofo.representedObject = String("com.apple.keylayout.ZhuyinBopomofo") - basisKeyboardLayoutButton.menu?.addItem(menuItem_AppleZhuyinBopomofo) + if language == "auto" { + autoMUISelectItem = menuItem + } - let menuItem_AppleZhuyinEten = NSMenuItem() - menuItem_AppleZhuyinEten.title = String(format: NSLocalizedString("Apple Zhuyin Eten", comment: "")) - menuItem_AppleZhuyinEten.representedObject = String("com.apple.keylayout.ZhuyinEten") - basisKeyboardLayoutButton.menu?.addItem(menuItem_AppleZhuyinEten) + if !appleLanguages.isEmpty { + if appleLanguages[0] == language { + chosenLanguageItem = menuItem + } + } + uiLanguageButton.menu?.addItem(menuItem) + } - let basisKeyboardLayoutID = mgrPrefs.basisKeyboardLayout + currentLanguageSelectItem = chosenLanguageItem ?? autoMUISelectItem + uiLanguageButton.select(currentLanguageSelectItem) - for source in list { - if let categoryPtr = TISGetInputSourceProperty(source, kTISPropertyInputSourceCategory) { - let category = Unmanaged.fromOpaque(categoryPtr).takeUnretainedValue() - if category != kTISCategoryKeyboardInputSource { - continue - } - } else { - continue - } + let list = TISCreateInputSourceList(nil, true).takeRetainedValue() as! [TISInputSource] + var usKeyboardLayoutItem: NSMenuItem? = nil + var chosenBaseKeyboardLayoutItem: NSMenuItem? = nil - if let asciiCapablePtr = TISGetInputSourceProperty(source, kTISPropertyInputSourceIsASCIICapable) { - let asciiCapable = Unmanaged.fromOpaque(asciiCapablePtr).takeUnretainedValue() - if asciiCapable != kCFBooleanTrue { - continue - } - } else { - continue - } + basisKeyboardLayoutButton.menu?.removeAllItems() - if let sourceTypePtr = TISGetInputSourceProperty(source, kTISPropertyInputSourceType) { - let sourceType = Unmanaged.fromOpaque(sourceTypePtr).takeUnretainedValue() - if sourceType != kTISTypeKeyboardLayout { - continue - } - } else { - continue - } + let itmAppleZhuyinBopomofo = NSMenuItem() + itmAppleZhuyinBopomofo.title = String( + format: NSLocalizedString("Apple Zhuyin Bopomofo", comment: "")) + itmAppleZhuyinBopomofo.representedObject = String( + "com.apple.keylayout.ZhuyinBopomofo") + basisKeyboardLayoutButton.menu?.addItem(itmAppleZhuyinBopomofo) - guard let sourceIDPtr = TISGetInputSourceProperty(source, kTISPropertyInputSourceID), - let localizedNamePtr = TISGetInputSourceProperty(source, kTISPropertyLocalizedName) else { - continue - } + let itmAppleZhuyinEten = NSMenuItem() + itmAppleZhuyinEten.title = String( + format: NSLocalizedString("Apple Zhuyin Eten", comment: "")) + itmAppleZhuyinEten.representedObject = String("com.apple.keylayout.ZhuyinEten") + basisKeyboardLayoutButton.menu?.addItem(itmAppleZhuyinEten) - let sourceID = String(Unmanaged.fromOpaque(sourceIDPtr).takeUnretainedValue()) - let localizedName = String(Unmanaged.fromOpaque(localizedNamePtr).takeUnretainedValue()) + let basisKeyboardLayoutID = mgrPrefs.basisKeyboardLayout - let menuItem = NSMenuItem() - menuItem.title = localizedName - menuItem.representedObject = sourceID + for source in list { + if let categoryPtr = TISGetInputSourceProperty(source, kTISPropertyInputSourceCategory) { + let category = Unmanaged.fromOpaque(categoryPtr).takeUnretainedValue() + if category != kTISCategoryKeyboardInputSource { + continue + } + } else { + continue + } - if sourceID == "com.apple.keylayout.US" { - usKeyboardLayoutItem = menuItem - } - if basisKeyboardLayoutID == sourceID { - chosenBaseKeyboardLayoutItem = menuItem - } - basisKeyboardLayoutButton.menu?.addItem(menuItem) - } + if let asciiCapablePtr = TISGetInputSourceProperty( + source, kTISPropertyInputSourceIsASCIICapable) + { + let asciiCapable = Unmanaged.fromOpaque(asciiCapablePtr) + .takeUnretainedValue() + if asciiCapable != kCFBooleanTrue { + continue + } + } else { + continue + } - switch basisKeyboardLayoutID { - case "com.apple.keylayout.ZhuyinBopomofo": - chosenBaseKeyboardLayoutItem = menuItem_AppleZhuyinBopomofo - case "com.apple.keylayout.ZhuyinEten": - chosenBaseKeyboardLayoutItem = menuItem_AppleZhuyinEten - default: - break // nothing to do - } + if let sourceTypePtr = TISGetInputSourceProperty(source, kTISPropertyInputSourceType) { + let sourceType = Unmanaged.fromOpaque(sourceTypePtr).takeUnretainedValue() + if sourceType != kTISTypeKeyboardLayout { + continue + } + } else { + continue + } - basisKeyboardLayoutButton.select(chosenBaseKeyboardLayoutItem ?? usKeyboardLayoutItem) + guard let sourceIDPtr = TISGetInputSourceProperty(source, kTISPropertyInputSourceID), + let localizedNamePtr = TISGetInputSourceProperty(source, kTISPropertyLocalizedName) + else { + continue + } - selectionKeyComboBox.usesDataSource = false - selectionKeyComboBox.removeAllItems() - selectionKeyComboBox.addItems(withObjectValues: mgrPrefs.suggestedCandidateKeys) + let sourceID = String(Unmanaged.fromOpaque(sourceIDPtr).takeUnretainedValue()) + let localizedName = String( + Unmanaged.fromOpaque(localizedNamePtr).takeUnretainedValue()) - var candidateSelectionKeys = mgrPrefs.candidateKeys - if candidateSelectionKeys.isEmpty { - candidateSelectionKeys = mgrPrefs.defaultCandidateKeys - } + let menuItem = NSMenuItem() + menuItem.title = localizedName + menuItem.representedObject = sourceID - selectionKeyComboBox.stringValue = candidateSelectionKeys - } + if sourceID == "com.apple.keylayout.US" { + usKeyboardLayoutItem = menuItem + } + if basisKeyboardLayoutID == sourceID { + chosenBaseKeyboardLayoutItem = menuItem + } + basisKeyboardLayoutButton.menu?.addItem(menuItem) + } - // 這裡有必要加上這段處理,用來確保藉由偏好設定介面動過的 CNS 開關能夠立刻生效。 - // 所有涉及到語言模型開關的內容均需要這樣處理。 - @IBAction func toggleCNSSupport(_ sender: Any) { - mgrLangModel.setCNSEnabled(mgrPrefs.cns11643Enabled) - } + switch basisKeyboardLayoutID { + case "com.apple.keylayout.ZhuyinBopomofo": + chosenBaseKeyboardLayoutItem = itmAppleZhuyinBopomofo + case "com.apple.keylayout.ZhuyinEten": + chosenBaseKeyboardLayoutItem = itmAppleZhuyinEten + default: + break // nothing to do + } - @IBAction func toggleSymbolInputEnabled(_ sender: Any) { - mgrLangModel.setSymbolEnabled(mgrPrefs.symbolInputEnabled) - } + basisKeyboardLayoutButton.select(chosenBaseKeyboardLayoutItem ?? usKeyboardLayoutItem) - @IBAction func toggleTrad2KangXiAction(_ sender: Any) { - if chkTrad2KangXi.state == .on && chkTrad2JISShinjitai.state == .on { - mgrPrefs.toggleShiftJISShinjitaiOutputEnabled() - } - } + selectionKeyComboBox.usesDataSource = false + selectionKeyComboBox.removeAllItems() + selectionKeyComboBox.addItems(withObjectValues: mgrPrefs.suggestedCandidateKeys) - @IBAction func toggleTrad2JISShinjitaiAction(_ sender: Any) { - if chkTrad2KangXi.state == .on && chkTrad2JISShinjitai.state == .on { - mgrPrefs.toggleChineseConversionEnabled() - } - } + var candidateSelectionKeys = mgrPrefs.candidateKeys + if candidateSelectionKeys.isEmpty { + candidateSelectionKeys = mgrPrefs.defaultCandidateKeys + } - @IBAction func updateBasisKeyboardLayoutAction(_ sender: Any) { - if let sourceID = basisKeyboardLayoutButton.selectedItem?.representedObject as? String { - mgrPrefs.basisKeyboardLayout = sourceID - } - } - - @IBAction func updateUiLanguageAction(_ sender: Any) { - if let selectItem = uiLanguageButton.selectedItem { - if currentLanguageSelectItem == selectItem { - return - } - } - if let language = uiLanguageButton.selectedItem?.representedObject as? String { - if (language != "auto") { - mgrPrefs.appleLanguages = [language] - } - else { - UserDefaults.standard.removeObject(forKey: "AppleLanguages") - } - - NSLog("vChewing App self-terminated due to UI language change.") - NSApplication.shared.terminate(nil) - } - } + selectionKeyComboBox.stringValue = candidateSelectionKeys + } - @IBAction func clickedWhetherIMEShouldNotFartToggleAction(_ sender: Any) { - clsSFX.beep() - } + // 這裡有必要加上這段處理,用來確保藉由偏好設定介面動過的 CNS 開關能夠立刻生效。 + // 所有涉及到語言模型開關的內容均需要這樣處理。 + @IBAction func toggleCNSSupport(_ sender: Any) { + mgrLangModel.setCNSEnabled(mgrPrefs.cns11643Enabled) + } - @IBAction func changeSelectionKeyAction(_ sender: Any) { - guard let keys = (sender as AnyObject).stringValue?.trimmingCharacters(in: .whitespacesAndNewlines).charDeDuplicate else { - return - } - do { - try mgrPrefs.validate(candidateKeys: keys) - mgrPrefs.candidateKeys = keys - selectionKeyComboBox.stringValue = mgrPrefs.candidateKeys - } - catch mgrPrefs.CandidateKeyError.empty { - selectionKeyComboBox.stringValue = mgrPrefs.candidateKeys - } - catch { - if let window = window { - let alert = NSAlert(error: error) - alert.beginSheetModal(for: window) { response in - self.selectionKeyComboBox.stringValue = mgrPrefs.candidateKeys - } - clsSFX.beep() - } - } - } + @IBAction func toggleSymbolInputEnabled(_ sender: Any) { + mgrLangModel.setSymbolEnabled(mgrPrefs.symbolInputEnabled) + } - @IBAction func resetSpecifiedUserDataFolder(_ sender: Any) { - UserDefaults.standard.removeObject(forKey: "UserDataFolderSpecified") - IME.initLangModels(userOnly: true) - } + @IBAction func toggleTrad2KangXiAction(_ sender: Any) { + if chkTrad2KangXi.state == .on && chkTrad2JISShinjitai.state == .on { + mgrPrefs.toggleShiftJISShinjitaiOutputEnabled() + } + } - @IBAction func chooseUserDataFolderToSpecify(_ sender: Any) { - IME.dlgOpenPath.title = NSLocalizedString("Choose your desired user data folder.", comment: ""); - IME.dlgOpenPath.showsResizeIndicator = true; - IME.dlgOpenPath.showsHiddenFiles = true; - IME.dlgOpenPath.canChooseFiles = false; - IME.dlgOpenPath.canChooseDirectories = true; + @IBAction func toggleTrad2JISShinjitaiAction(_ sender: Any) { + if chkTrad2KangXi.state == .on && chkTrad2JISShinjitai.state == .on { + mgrPrefs.toggleChineseConversionEnabled() + } + } - let PreviousFolderValidity = mgrLangModel.checkIfSpecifiedUserDataFolderValid(NSString(string: mgrPrefs.userDataFolderSpecified).expandingTildeInPath) + @IBAction func updateBasisKeyboardLayoutAction(_ sender: Any) { + if let sourceID = basisKeyboardLayoutButton.selectedItem?.representedObject as? String { + mgrPrefs.basisKeyboardLayout = sourceID + } + } - if self.window != nil { - IME.dlgOpenPath.beginSheetModal(for: self.window!) { result in - if result == NSApplication.ModalResponse.OK { - if (IME.dlgOpenPath.url != nil) { - if (mgrLangModel.checkIfSpecifiedUserDataFolderValid(IME.dlgOpenPath.url!.path)) { - mgrPrefs.userDataFolderSpecified = IME.dlgOpenPath.url!.path - IME.initLangModels(userOnly: true) - } else { - clsSFX.beep() - if !PreviousFolderValidity { - self.resetSpecifiedUserDataFolder(self) - } - return - } - } - } else { - if !PreviousFolderValidity { - self.resetSpecifiedUserDataFolder(self) - } - return - } - } - } // End If self.window != nil - } // End IBAction + @IBAction func updateUiLanguageAction(_ sender: Any) { + if let selectItem = uiLanguageButton.selectedItem { + if currentLanguageSelectItem == selectItem { + return + } + } + if let language = uiLanguageButton.selectedItem?.representedObject as? String { + if language != "auto" { + mgrPrefs.appleLanguages = [language] + } else { + UserDefaults.standard.removeObject(forKey: "AppleLanguages") + } + + NSLog("vChewing App self-terminated due to UI language change.") + NSApplication.shared.terminate(nil) + } + } + + @IBAction func clickedWhetherIMEShouldNotFartToggleAction(_ sender: Any) { + clsSFX.beep() + } + + @IBAction func changeSelectionKeyAction(_ sender: Any) { + guard + let keys = (sender as AnyObject).stringValue?.trimmingCharacters( + in: .whitespacesAndNewlines + ) + .charDeDuplicate + else { + return + } + do { + try mgrPrefs.validate(candidateKeys: keys) + mgrPrefs.candidateKeys = keys + selectionKeyComboBox.stringValue = mgrPrefs.candidateKeys + } catch mgrPrefs.CandidateKeyError.empty { + selectionKeyComboBox.stringValue = mgrPrefs.candidateKeys + } catch { + if let window = window { + let alert = NSAlert(error: error) + alert.beginSheetModal(for: window) { response in + self.selectionKeyComboBox.stringValue = mgrPrefs.candidateKeys + } + clsSFX.beep() + } + } + } + + @IBAction func resetSpecifiedUserDataFolder(_ sender: Any) { + UserDefaults.standard.removeObject(forKey: "UserDataFolderSpecified") + IME.initLangModels(userOnly: true) + } + + @IBAction func chooseUserDataFolderToSpecify(_ sender: Any) { + IME.dlgOpenPath.title = NSLocalizedString( + "Choose your desired user data folder.", comment: "") + IME.dlgOpenPath.showsResizeIndicator = true + IME.dlgOpenPath.showsHiddenFiles = true + IME.dlgOpenPath.canChooseFiles = false + IME.dlgOpenPath.canChooseDirectories = true + + let bolPreviousFolderValidity = mgrLangModel.checkIfSpecifiedUserDataFolderValid( + NSString(string: mgrPrefs.userDataFolderSpecified).expandingTildeInPath) + + if self.window != nil { + IME.dlgOpenPath.beginSheetModal(for: self.window!) { result in + if result == NSApplication.ModalResponse.OK { + if IME.dlgOpenPath.url != nil { + if mgrLangModel.checkIfSpecifiedUserDataFolderValid( + IME.dlgOpenPath.url!.path) + { + mgrPrefs.userDataFolderSpecified = IME.dlgOpenPath.url!.path + IME.initLangModels(userOnly: true) + } else { + clsSFX.beep() + if !bolPreviousFolderValidity { + self.resetSpecifiedUserDataFolder(self) + } + return + } + } + } else { + if !bolPreviousFolderValidity { + self.resetSpecifiedUserDataFolder(self) + } + return + } + } + } // End If self.window != nil + } // End IBAction } diff --git a/UserPhraseEditor/AppDelegate.swift b/UserPhraseEditor/AppDelegate.swift index dd33c82a..e5e44d25 100644 --- a/UserPhraseEditor/AppDelegate.swift +++ b/UserPhraseEditor/AppDelegate.swift @@ -1,19 +1,25 @@ // Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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 @@ -21,31 +27,31 @@ import Cocoa @NSApplicationMain class AppDelegate: NSObject, NSApplicationDelegate { - private var ctlAboutWindowInstance: ctlAboutWindow? // New About Window - - func applicationDidFinishLaunching(_ aNotification: Notification) { - // Insert code here to initialize your application - } + private var ctlAboutWindowInstance: ctlAboutWindow? // New About Window - func applicationWillTerminate(_ aNotification: Notification) { - // Insert code here to tear down your application - } + func applicationDidFinishLaunching(_ aNotification: Notification) { + // Insert code here to initialize your application + } - func applicationShouldTerminate(_ sender: NSApplication)-> NSApplication.TerminateReply { - return .terminateNow - } - // New About Window - @objc func showAbout() { - if (ctlAboutWindowInstance == nil) { - ctlAboutWindowInstance = ctlAboutWindow.init(windowNibName: "frmAboutWindow") - } - ctlAboutWindowInstance?.window?.center() - ctlAboutWindowInstance?.window?.orderFrontRegardless() // 逼著關於視窗往最前方顯示 - ctlAboutWindowInstance?.window?.level = .statusBar - } - // Call the New About Window - @IBAction func about(_ sender: Any) { - (NSApp.delegate as? AppDelegate)?.showAbout() - NSApplication.shared.activate(ignoringOtherApps: true) - } + func applicationWillTerminate(_ aNotification: Notification) { + // Insert code here to tear down your application + } + + func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply { + return .terminateNow + } + // New About Window + @objc func showAbout() { + if ctlAboutWindowInstance == nil { + ctlAboutWindowInstance = ctlAboutWindow.init(windowNibName: "frmAboutWindow") + } + ctlAboutWindowInstance?.window?.center() + ctlAboutWindowInstance?.window?.orderFrontRegardless() // 逼著關於視窗往最前方顯示 + ctlAboutWindowInstance?.window?.level = .statusBar + } + // Call the New About Window + @IBAction func about(_ sender: Any) { + (NSApp.delegate as? AppDelegate)?.showAbout() + NSApplication.shared.activate(ignoringOtherApps: true) + } } diff --git a/UserPhraseEditor/Content.swift b/UserPhraseEditor/Content.swift index 50155a53..3cf4acf6 100644 --- a/UserPhraseEditor/Content.swift +++ b/UserPhraseEditor/Content.swift @@ -1,41 +1,47 @@ // Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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 Foundation import Cocoa +import Foundation class Content: NSObject { - @objc dynamic var contentString = "" - - public init(contentString: String) { - self.contentString = contentString - } - + @objc dynamic var contentString = "" + + public init(contentString: String) { + self.contentString = contentString + } + } extension Content { - - func read(from data: Data) { - contentString = String(bytes: data, encoding: .utf8)! - } - - func data() -> Data? { - return contentString.data(using: .utf8) - } - + + func read(from data: Data) { + contentString = String(bytes: data, encoding: .utf8)! + } + + func data() -> Data? { + return contentString.data(using: .utf8) + } + } diff --git a/UserPhraseEditor/Document.swift b/UserPhraseEditor/Document.swift index 9f498207..6777e77f 100644 --- a/UserPhraseEditor/Document.swift +++ b/UserPhraseEditor/Document.swift @@ -1,131 +1,145 @@ // Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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 class Document: NSDocument { - - @objc var content = Content(contentString: "") - var contentViewController: ViewController! - - override init() { - super.init() - // Add your subclass-specific initialization here. - } - - // MARK: - Enablers - - // This enables auto save. - override class var autosavesInPlace: Bool { - return true - } - - // This enables asynchronous-writing. - override func canAsynchronouslyWrite(to url: URL, ofType typeName: String, for saveOperation: NSDocument.SaveOperationType) -> Bool { - return true - } - - // This enables asynchronous reading. - override class func canConcurrentlyReadDocuments(ofType: String) -> Bool { - return ofType == "public.plain-text" - } - - // MARK: - User Interface - - /// - Tag: makeWindowControllersExample - override func makeWindowControllers() { - // Returns the storyboard that contains your document window. - let storyboard = NSStoryboard(name: NSStoryboard.Name("Main"), bundle: nil) - if let windowController = - storyboard.instantiateController( - withIdentifier: NSStoryboard.SceneIdentifier("Document Window Controller")) as? NSWindowController { - addWindowController(windowController) - - // Set the view controller's represented object as your document. - if let contentVC = windowController.contentViewController as? ViewController { - contentVC.representedObject = content - contentViewController = contentVC - } - } - } - - // MARK: - Reading and Writing - - /// - Tag: readExample - override func read(from data: Data, ofType typeName: String) throws { - var strToDealWith = String(decoding: data, as: UTF8.self) - strToDealWith.formatConsolidate(HYPY2BPMF: false) - let processedIncomingData = Data(strToDealWith.utf8) - content.read(from: processedIncomingData) - } - - /// - Tag: writeExample - override func data(ofType typeName: String) throws -> Data { - var strToDealWith = content.contentString - strToDealWith.formatConsolidate(HYPY2BPMF: true) - let outputData = Data(strToDealWith.utf8) - return outputData - } - - // MARK: - Printing - - func thePrintInfo() -> NSPrintInfo { - let thePrintInfo = NSPrintInfo() - thePrintInfo.horizontalPagination = .fit - thePrintInfo.isHorizontallyCentered = false - thePrintInfo.isVerticallyCentered = false - - // One inch margin all the way around. - thePrintInfo.leftMargin = 72.0 - thePrintInfo.rightMargin = 72.0 - thePrintInfo.topMargin = 72.0 - thePrintInfo.bottomMargin = 72.0 - - printInfo.dictionary().setObject(NSNumber(value: true), - forKey: NSPrintInfo.AttributeKey.headerAndFooter as NSCopying) - - return thePrintInfo - } - - @objc - func printOperationDidRun( - _ printOperation: NSPrintOperation, success: Bool, contextInfo: UnsafeMutableRawPointer?) { - // Printing finished... - } - - @IBAction override func printDocument(_ sender: Any?) { - // Print the NSTextView. - - // Create a copy to manipulate for printing. - let pageSize = NSSize(width: (printInfo.paperSize.width), height: (printInfo.paperSize.height)) - let textView = NSTextView(frame: NSRect(x: 0.0, y: 0.0, width: pageSize.width, height: pageSize.height)) - - // Make sure we print on a white background. - textView.appearance = NSAppearance(named: .aqua) - - // Copy the attributed string. - textView.textStorage?.append(NSAttributedString(string: content.contentString)) - - let printOperation = NSPrintOperation(view: textView) - printOperation.runModal( - for: windowControllers[0].window!, - delegate: self, - didRun: #selector(printOperationDidRun(_:success:contextInfo:)), contextInfo: nil) - } - + + @objc var content = Content(contentString: "") + var contentViewController: ViewController! + + override init() { + super.init() + // Add your subclass-specific initialization here. + } + + // MARK: - Enablers + + // This enables auto save. + override class var autosavesInPlace: Bool { + return true + } + + // This enables asynchronous-writing. + override func canAsynchronouslyWrite( + to url: URL, ofType typeName: String, for saveOperation: NSDocument.SaveOperationType + ) -> Bool { + return true + } + + // This enables asynchronous reading. + override class func canConcurrentlyReadDocuments(ofType: String) -> Bool { + return ofType == "public.plain-text" + } + + // MARK: - User Interface + + /// - Tag: makeWindowControllersExample + override func makeWindowControllers() { + // Returns the storyboard that contains your document window. + let storyboard = NSStoryboard(name: NSStoryboard.Name("Main"), bundle: nil) + if let windowController = + storyboard.instantiateController( + withIdentifier: NSStoryboard.SceneIdentifier("Document Window Controller")) + as? NSWindowController + { + addWindowController(windowController) + + // Set the view controller's represented object as your document. + if let contentVC = windowController.contentViewController as? ViewController { + contentVC.representedObject = content + contentViewController = contentVC + } + } + } + + // MARK: - Reading and Writing + + /// - Tag: readExample + override func read(from data: Data, ofType typeName: String) throws { + var strToDealWith = String(decoding: data, as: UTF8.self) + strToDealWith.formatConsolidate(cnvHYPYtoBPMF: false) + let processedIncomingData = Data(strToDealWith.utf8) + content.read(from: processedIncomingData) + } + + /// - Tag: writeExample + override func data(ofType typeName: String) throws -> Data { + var strToDealWith = content.contentString + strToDealWith.formatConsolidate(cnvHYPYtoBPMF: true) + let outputData = Data(strToDealWith.utf8) + return outputData + } + + // MARK: - Printing + + func thePrintInfo() -> NSPrintInfo { + let thePrintInfo = NSPrintInfo() + thePrintInfo.horizontalPagination = .fit + thePrintInfo.isHorizontallyCentered = false + thePrintInfo.isVerticallyCentered = false + + // One inch margin all the way around. + thePrintInfo.leftMargin = 72.0 + thePrintInfo.rightMargin = 72.0 + thePrintInfo.topMargin = 72.0 + thePrintInfo.bottomMargin = 72.0 + + printInfo.dictionary().setObject( + NSNumber(value: true), + forKey: NSPrintInfo.AttributeKey.headerAndFooter as NSCopying) + + return thePrintInfo + } + + @objc + func printOperationDidRun( + _ printOperation: NSPrintOperation, success: Bool, contextInfo: UnsafeMutableRawPointer? + ) { + // Printing finished... + } + + @IBAction override func printDocument(_ sender: Any?) { + // Print the NSTextView. + + // Create a copy to manipulate for printing. + let pageSize = NSSize( + width: (printInfo.paperSize.width), height: (printInfo.paperSize.height)) + let textView = NSTextView( + frame: NSRect(x: 0.0, y: 0.0, width: pageSize.width, height: pageSize.height)) + + // Make sure we print on a white background. + textView.appearance = NSAppearance(named: .aqua) + + // Copy the attributed string. + textView.textStorage?.append(NSAttributedString(string: content.contentString)) + + let printOperation = NSPrintOperation(view: textView) + printOperation.runModal( + for: windowControllers[0].window!, + delegate: self, + didRun: #selector(printOperationDidRun(_:success:contextInfo:)), contextInfo: nil) + } + } diff --git a/UserPhraseEditor/StringExtension.swift b/UserPhraseEditor/StringExtension.swift index 08b1db62..bc6aa222 100644 --- a/UserPhraseEditor/StringExtension.swift +++ b/UserPhraseEditor/StringExtension.swift @@ -1,515 +1,523 @@ // Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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 Foundation extension String { - mutating func regReplace(pattern: String, replaceWith: String = "") { - // Ref: https://stackoverflow.com/a/40993403/4162914 && https://stackoverflow.com/a/71291137/4162914 - do { - let regex = try NSRegularExpression(pattern: pattern, options: [.caseInsensitive, .anchorsMatchLines]) - let range = NSRange(self.startIndex..., in: self) - self = regex.stringByReplacingMatches(in: self, options: [], range: range, withTemplate: replaceWith) - } catch { return } - } - mutating func selfReplace(_ strOf: String, _ strWith: String = "") { - self = self.replacingOccurrences(of: strOf, with: strWith) - } - mutating func formatConsolidate(HYPY2BPMF: Bool) { - // Step 1: Consolidating formats per line. - var strProcessed = self - // 預處理格式 - strProcessed = strProcessed.replacingOccurrences(of: " #MACOS", with: "") // 去掉 macOS 標記 - // CJKWhiteSpace (\x{3000}) to ASCII Space - // NonBreakWhiteSpace (\x{A0}) to ASCII Space - // Tab to ASCII Space - // 統整連續空格為一個 ASCII 空格 - strProcessed.regReplace(pattern: #"( +| +| +|\t+)+"#, replaceWith: " ") - strProcessed.regReplace(pattern: #"(^ | $)"#, replaceWith: "") // 去除行尾行首空格 - strProcessed.regReplace(pattern: #"(\f+|\r+|\n+)+"#, replaceWith: "\n") // CR & Form Feed to LF, 且去除重複行 - if strProcessed.prefix(1) == " " { // 去除檔案開頭空格 - strProcessed.removeFirst() - } - if strProcessed.suffix(1) == " " { // 去除檔案結尾空格 - strProcessed.removeLast() - } - var arrData = [""] - if HYPY2BPMF { - // Step 0: Convert HanyuPinyin to Bopomofo. - arrData = strProcessed.components(separatedBy: "\n") - strProcessed = "" // Reset its value - for lineData in arrData { - var varLineData = lineData - // 漢語拼音轉注音,得先從最長的可能的拼音組合開始轉起, - // 這樣等轉換到更短的可能的漢語拼音組合時就不會出錯。 - // 依此類推,聲調放在最後來轉換。 - varLineData.selfReplace("chuang", "ㄔㄨㄤ") - varLineData.selfReplace("shuang", "ㄕㄨㄤ") - varLineData.selfReplace("zhuang", "ㄓㄨㄤ") - varLineData.selfReplace("chang", "ㄔㄤ") - varLineData.selfReplace("cheng", "ㄔㄥ") - varLineData.selfReplace("chong", "ㄔㄨㄥ") - varLineData.selfReplace("chuai", "ㄔㄨㄞ") - varLineData.selfReplace("chuan", "ㄔㄨㄢ") - varLineData.selfReplace("guang", "ㄍㄨㄤ") - varLineData.selfReplace("huang", "ㄏㄨㄤ") - varLineData.selfReplace("jiang", "ㄐㄧㄤ") - varLineData.selfReplace("jiong", "ㄐㄩㄥ") - varLineData.selfReplace("kuang", "ㄎㄨㄤ") - varLineData.selfReplace("liang", "ㄌㄧㄤ") - varLineData.selfReplace("niang", "ㄋㄧㄤ") - varLineData.selfReplace("qiang", "ㄑㄧㄤ") - varLineData.selfReplace("qiong", "ㄑㄩㄥ") - varLineData.selfReplace("shang", "ㄕㄤ") - varLineData.selfReplace("sheng", "ㄕㄥ") - varLineData.selfReplace("shuai", "ㄕㄨㄞ") - varLineData.selfReplace("shuan", "ㄕㄨㄢ") - varLineData.selfReplace("xiang", "ㄒㄧㄤ") - varLineData.selfReplace("xiong", "ㄒㄩㄥ") - varLineData.selfReplace("zhang", "ㄓㄤ") - varLineData.selfReplace("zheng", "ㄓㄥ") - varLineData.selfReplace("zhong", "ㄓㄨㄥ") - varLineData.selfReplace("zhuai", "ㄓㄨㄞ") - varLineData.selfReplace("zhuan", "ㄓㄨㄢ") - varLineData.selfReplace("bang", "ㄅㄤ") - varLineData.selfReplace("beng", "ㄅㄥ") - varLineData.selfReplace("bian", "ㄅㄧㄢ") - varLineData.selfReplace("biao", "ㄅㄧㄠ") - varLineData.selfReplace("bing", "ㄅㄧㄥ") - varLineData.selfReplace("cang", "ㄘㄤ") - varLineData.selfReplace("ceng", "ㄘㄥ") - varLineData.selfReplace("chai", "ㄔㄞ") - varLineData.selfReplace("chan", "ㄔㄢ") - varLineData.selfReplace("chao", "ㄔㄠ") - varLineData.selfReplace("chen", "ㄔㄣ") - varLineData.selfReplace("chou", "ㄔㄡ") - varLineData.selfReplace("chua", "ㄔㄨㄚ") - varLineData.selfReplace("chui", "ㄔㄨㄟ") - varLineData.selfReplace("chun", "ㄔㄨㄣ") - varLineData.selfReplace("chuo", "ㄔㄨㄛ") - varLineData.selfReplace("cong", "ㄘㄨㄥ") - varLineData.selfReplace("cuan", "ㄘㄨㄢ") - varLineData.selfReplace("dang", "ㄉㄤ") - varLineData.selfReplace("deng", "ㄉㄥ") - varLineData.selfReplace("dian", "ㄉㄧㄢ") - varLineData.selfReplace("diao", "ㄉㄧㄠ") - varLineData.selfReplace("ding", "ㄉㄧㄥ") - varLineData.selfReplace("dong", "ㄉㄨㄥ") - varLineData.selfReplace("duan", "ㄉㄨㄢ") - varLineData.selfReplace("fang", "ㄈㄤ") - varLineData.selfReplace("feng", "ㄈㄥ") - varLineData.selfReplace("fiao", "ㄈㄧㄠ") - varLineData.selfReplace("fong", "ㄈㄨㄥ") - varLineData.selfReplace("gang", "ㄍㄤ") - varLineData.selfReplace("geng", "ㄍㄥ") - varLineData.selfReplace("giao", "ㄍㄧㄠ") - varLineData.selfReplace("gong", "ㄍㄨㄥ") - varLineData.selfReplace("guai", "ㄍㄨㄞ") - varLineData.selfReplace("guan", "ㄍㄨㄢ") - varLineData.selfReplace("hang", "ㄏㄤ") - varLineData.selfReplace("heng", "ㄏㄥ") - varLineData.selfReplace("hong", "ㄏㄨㄥ") - varLineData.selfReplace("huai", "ㄏㄨㄞ") - varLineData.selfReplace("huan", "ㄏㄨㄢ") - varLineData.selfReplace("jian", "ㄐㄧㄢ") - varLineData.selfReplace("jiao", "ㄐㄧㄠ") - varLineData.selfReplace("jing", "ㄐㄧㄥ") - varLineData.selfReplace("juan", "ㄐㄩㄢ") - varLineData.selfReplace("kang", "ㄎㄤ") - varLineData.selfReplace("keng", "ㄎㄥ") - varLineData.selfReplace("kong", "ㄎㄨㄥ") - varLineData.selfReplace("kuai", "ㄎㄨㄞ") - varLineData.selfReplace("kuan", "ㄎㄨㄢ") - varLineData.selfReplace("lang", "ㄌㄤ") - varLineData.selfReplace("leng", "ㄌㄥ") - varLineData.selfReplace("lian", "ㄌㄧㄢ") - varLineData.selfReplace("liao", "ㄌㄧㄠ") - varLineData.selfReplace("ling", "ㄌㄧㄥ") - varLineData.selfReplace("long", "ㄌㄨㄥ") - varLineData.selfReplace("luan", "ㄌㄨㄢ") - varLineData.selfReplace("lvan", "ㄌㄩㄢ") - varLineData.selfReplace("mang", "ㄇㄤ") - varLineData.selfReplace("meng", "ㄇㄥ") - varLineData.selfReplace("mian", "ㄇㄧㄢ") - varLineData.selfReplace("miao", "ㄇㄧㄠ") - varLineData.selfReplace("ming", "ㄇㄧㄥ") - varLineData.selfReplace("nang", "ㄋㄤ") - varLineData.selfReplace("neng", "ㄋㄥ") - varLineData.selfReplace("nian", "ㄋㄧㄢ") - varLineData.selfReplace("niao", "ㄋㄧㄠ") - varLineData.selfReplace("ning", "ㄋㄧㄥ") - varLineData.selfReplace("nong", "ㄋㄨㄥ") - varLineData.selfReplace("nuan", "ㄋㄨㄢ") - varLineData.selfReplace("pang", "ㄆㄤ") - varLineData.selfReplace("peng", "ㄆㄥ") - varLineData.selfReplace("pian", "ㄆㄧㄢ") - varLineData.selfReplace("piao", "ㄆㄧㄠ") - varLineData.selfReplace("ping", "ㄆㄧㄥ") - varLineData.selfReplace("qian", "ㄑㄧㄢ") - varLineData.selfReplace("qiao", "ㄑㄧㄠ") - varLineData.selfReplace("qing", "ㄑㄧㄥ") - varLineData.selfReplace("quan", "ㄑㄩㄢ") - varLineData.selfReplace("rang", "ㄖㄤ") - varLineData.selfReplace("reng", "ㄖㄥ") - varLineData.selfReplace("rong", "ㄖㄨㄥ") - varLineData.selfReplace("ruan", "ㄖㄨㄢ") - varLineData.selfReplace("sang", "ㄙㄤ") - varLineData.selfReplace("seng", "ㄙㄥ") - varLineData.selfReplace("shai", "ㄕㄞ") - varLineData.selfReplace("shan", "ㄕㄢ") - varLineData.selfReplace("shao", "ㄕㄠ") - varLineData.selfReplace("shei", "ㄕㄟ") - varLineData.selfReplace("shen", "ㄕㄣ") - varLineData.selfReplace("shou", "ㄕㄡ") - varLineData.selfReplace("shua", "ㄕㄨㄚ") - varLineData.selfReplace("shui", "ㄕㄨㄟ") - varLineData.selfReplace("shun", "ㄕㄨㄣ") - varLineData.selfReplace("shuo", "ㄕㄨㄛ") - varLineData.selfReplace("song", "ㄙㄨㄥ") - varLineData.selfReplace("suan", "ㄙㄨㄢ") - varLineData.selfReplace("tang", "ㄊㄤ") - varLineData.selfReplace("teng", "ㄊㄥ") - varLineData.selfReplace("tian", "ㄊㄧㄢ") - varLineData.selfReplace("tiao", "ㄊㄧㄠ") - varLineData.selfReplace("ting", "ㄊㄧㄥ") - varLineData.selfReplace("tong", "ㄊㄨㄥ") - varLineData.selfReplace("tuan", "ㄊㄨㄢ") - varLineData.selfReplace("wang", "ㄨㄤ") - varLineData.selfReplace("weng", "ㄨㄥ") - varLineData.selfReplace("xian", "ㄒㄧㄢ") - varLineData.selfReplace("xiao", "ㄒㄧㄠ") - varLineData.selfReplace("xing", "ㄒㄧㄥ") - varLineData.selfReplace("xuan", "ㄒㄩㄢ") - varLineData.selfReplace("yang", "ㄧㄤ") - varLineData.selfReplace("ying", "ㄧㄥ") - varLineData.selfReplace("yong", "ㄩㄥ") - varLineData.selfReplace("yuan", "ㄩㄢ") - varLineData.selfReplace("zang", "ㄗㄤ") - varLineData.selfReplace("zeng", "ㄗㄥ") - varLineData.selfReplace("zhai", "ㄓㄞ") - varLineData.selfReplace("zhan", "ㄓㄢ") - varLineData.selfReplace("zhao", "ㄓㄠ") - varLineData.selfReplace("zhei", "ㄓㄟ") - varLineData.selfReplace("zhen", "ㄓㄣ") - varLineData.selfReplace("zhou", "ㄓㄡ") - varLineData.selfReplace("zhua", "ㄓㄨㄚ") - varLineData.selfReplace("zhui", "ㄓㄨㄟ") - varLineData.selfReplace("zhun", "ㄓㄨㄣ") - varLineData.selfReplace("zhuo", "ㄓㄨㄛ") - varLineData.selfReplace("zong", "ㄗㄨㄥ") - varLineData.selfReplace("zuan", "ㄗㄨㄢ") - varLineData.selfReplace("jun", "ㄐㄩㄣ") - varLineData.selfReplace("ang", "ㄤ") - varLineData.selfReplace("bai", "ㄅㄞ") - varLineData.selfReplace("ban", "ㄅㄢ") - varLineData.selfReplace("bao", "ㄅㄠ") - varLineData.selfReplace("bei", "ㄅㄟ") - varLineData.selfReplace("ben", "ㄅㄣ") - varLineData.selfReplace("bie", "ㄅㄧㄝ") - varLineData.selfReplace("bin", "ㄅㄧㄣ") - varLineData.selfReplace("cai", "ㄘㄞ") - varLineData.selfReplace("can", "ㄘㄢ") - varLineData.selfReplace("cao", "ㄘㄠ") - varLineData.selfReplace("cei", "ㄘㄟ") - varLineData.selfReplace("cen", "ㄘㄣ") - varLineData.selfReplace("cha", "ㄔㄚ") - varLineData.selfReplace("che", "ㄔㄜ") - varLineData.selfReplace("chi", "ㄔ") - varLineData.selfReplace("chu", "ㄔㄨ") - varLineData.selfReplace("cou", "ㄘㄡ") - varLineData.selfReplace("cui", "ㄘㄨㄟ") - varLineData.selfReplace("cun", "ㄘㄨㄣ") - varLineData.selfReplace("cuo", "ㄘㄨㄛ") - varLineData.selfReplace("dai", "ㄉㄞ") - varLineData.selfReplace("dan", "ㄉㄢ") - varLineData.selfReplace("dao", "ㄉㄠ") - varLineData.selfReplace("dei", "ㄉㄟ") - varLineData.selfReplace("den", "ㄉㄣ") - varLineData.selfReplace("dia", "ㄉㄧㄚ") - varLineData.selfReplace("die", "ㄉㄧㄝ") - varLineData.selfReplace("diu", "ㄉㄧㄡ") - varLineData.selfReplace("dou", "ㄉㄡ") - varLineData.selfReplace("dui", "ㄉㄨㄟ") - varLineData.selfReplace("dun", "ㄉㄨㄣ") - varLineData.selfReplace("duo", "ㄉㄨㄛ") - varLineData.selfReplace("eng", "ㄥ") - varLineData.selfReplace("fan", "ㄈㄢ") - varLineData.selfReplace("fei", "ㄈㄟ") - varLineData.selfReplace("fen", "ㄈㄣ") - varLineData.selfReplace("fou", "ㄈㄡ") - varLineData.selfReplace("gai", "ㄍㄞ") - varLineData.selfReplace("gan", "ㄍㄢ") - varLineData.selfReplace("gao", "ㄍㄠ") - varLineData.selfReplace("gei", "ㄍㄟ") - varLineData.selfReplace("gin", "ㄍㄧㄣ") - varLineData.selfReplace("gen", "ㄍㄣ") - varLineData.selfReplace("gou", "ㄍㄡ") - varLineData.selfReplace("gua", "ㄍㄨㄚ") - varLineData.selfReplace("gue", "ㄍㄨㄜ") - varLineData.selfReplace("gui", "ㄍㄨㄟ") - varLineData.selfReplace("gun", "ㄍㄨㄣ") - varLineData.selfReplace("guo", "ㄍㄨㄛ") - varLineData.selfReplace("hai", "ㄏㄞ") - varLineData.selfReplace("han", "ㄏㄢ") - varLineData.selfReplace("hao", "ㄏㄠ") - varLineData.selfReplace("hei", "ㄏㄟ") - varLineData.selfReplace("hen", "ㄏㄣ") - varLineData.selfReplace("hou", "ㄏㄡ") - varLineData.selfReplace("hua", "ㄏㄨㄚ") - varLineData.selfReplace("hui", "ㄏㄨㄟ") - varLineData.selfReplace("hun", "ㄏㄨㄣ") - varLineData.selfReplace("huo", "ㄏㄨㄛ") - varLineData.selfReplace("jia", "ㄐㄧㄚ") - varLineData.selfReplace("jie", "ㄐㄧㄝ") - varLineData.selfReplace("jin", "ㄐㄧㄣ") - varLineData.selfReplace("jiu", "ㄐㄧㄡ") - varLineData.selfReplace("jue", "ㄐㄩㄝ") - varLineData.selfReplace("kai", "ㄎㄞ") - varLineData.selfReplace("kan", "ㄎㄢ") - varLineData.selfReplace("kao", "ㄎㄠ") - varLineData.selfReplace("ken", "ㄎㄣ") - varLineData.selfReplace("kiu", "ㄎㄧㄡ") - varLineData.selfReplace("kou", "ㄎㄡ") - varLineData.selfReplace("kua", "ㄎㄨㄚ") - varLineData.selfReplace("kui", "ㄎㄨㄟ") - varLineData.selfReplace("kun", "ㄎㄨㄣ") - varLineData.selfReplace("kuo", "ㄎㄨㄛ") - varLineData.selfReplace("lai", "ㄌㄞ") - varLineData.selfReplace("lan", "ㄌㄢ") - varLineData.selfReplace("lao", "ㄌㄠ") - varLineData.selfReplace("lei", "ㄌㄟ") - varLineData.selfReplace("lia", "ㄌㄧㄚ") - varLineData.selfReplace("lie", "ㄌㄧㄝ") - varLineData.selfReplace("lin", "ㄌㄧㄣ") - varLineData.selfReplace("liu", "ㄌㄧㄡ") - varLineData.selfReplace("lou", "ㄌㄡ") - varLineData.selfReplace("lun", "ㄌㄨㄣ") - varLineData.selfReplace("luo", "ㄌㄨㄛ") - varLineData.selfReplace("lve", "ㄌㄩㄝ") - varLineData.selfReplace("mai", "ㄇㄞ") - varLineData.selfReplace("man", "ㄇㄢ") - varLineData.selfReplace("mao", "ㄇㄠ") - varLineData.selfReplace("mei", "ㄇㄟ") - varLineData.selfReplace("men", "ㄇㄣ") - varLineData.selfReplace("mie", "ㄇㄧㄝ") - varLineData.selfReplace("min", "ㄇㄧㄣ") - varLineData.selfReplace("miu", "ㄇㄧㄡ") - varLineData.selfReplace("mou", "ㄇㄡ") - varLineData.selfReplace("nai", "ㄋㄞ") - varLineData.selfReplace("nan", "ㄋㄢ") - varLineData.selfReplace("nao", "ㄋㄠ") - varLineData.selfReplace("nei", "ㄋㄟ") - varLineData.selfReplace("nen", "ㄋㄣ") - varLineData.selfReplace("nie", "ㄋㄧㄝ") - varLineData.selfReplace("nin", "ㄋㄧㄣ") - varLineData.selfReplace("niu", "ㄋㄧㄡ") - varLineData.selfReplace("nou", "ㄋㄡ") - varLineData.selfReplace("nui", "ㄋㄨㄟ") - varLineData.selfReplace("nun", "ㄋㄨㄣ") - varLineData.selfReplace("nuo", "ㄋㄨㄛ") - varLineData.selfReplace("nve", "ㄋㄩㄝ") - varLineData.selfReplace("pai", "ㄆㄞ") - varLineData.selfReplace("pan", "ㄆㄢ") - varLineData.selfReplace("pao", "ㄆㄠ") - varLineData.selfReplace("pei", "ㄆㄟ") - varLineData.selfReplace("pen", "ㄆㄣ") - varLineData.selfReplace("pia", "ㄆㄧㄚ") - varLineData.selfReplace("pie", "ㄆㄧㄝ") - varLineData.selfReplace("pin", "ㄆㄧㄣ") - varLineData.selfReplace("pou", "ㄆㄡ") - varLineData.selfReplace("qia", "ㄑㄧㄚ") - varLineData.selfReplace("qie", "ㄑㄧㄝ") - varLineData.selfReplace("qin", "ㄑㄧㄣ") - varLineData.selfReplace("qiu", "ㄑㄧㄡ") - varLineData.selfReplace("que", "ㄑㄩㄝ") - varLineData.selfReplace("qun", "ㄑㄩㄣ") - varLineData.selfReplace("ran", "ㄖㄢ") - varLineData.selfReplace("rao", "ㄖㄠ") - varLineData.selfReplace("ren", "ㄖㄣ") - varLineData.selfReplace("rou", "ㄖㄡ") - varLineData.selfReplace("rui", "ㄖㄨㄟ") - varLineData.selfReplace("run", "ㄖㄨㄣ") - varLineData.selfReplace("ruo", "ㄖㄨㄛ") - varLineData.selfReplace("sai", "ㄙㄞ") - varLineData.selfReplace("san", "ㄙㄢ") - varLineData.selfReplace("sao", "ㄙㄠ") - varLineData.selfReplace("sei", "ㄙㄟ") - varLineData.selfReplace("sen", "ㄙㄣ") - varLineData.selfReplace("sha", "ㄕㄚ") - varLineData.selfReplace("she", "ㄕㄜ") - varLineData.selfReplace("shi", "ㄕ") - varLineData.selfReplace("shu", "ㄕㄨ") - varLineData.selfReplace("sou", "ㄙㄡ") - varLineData.selfReplace("sui", "ㄙㄨㄟ") - varLineData.selfReplace("sun", "ㄙㄨㄣ") - varLineData.selfReplace("suo", "ㄙㄨㄛ") - varLineData.selfReplace("tai", "ㄊㄞ") - varLineData.selfReplace("tan", "ㄊㄢ") - varLineData.selfReplace("tao", "ㄊㄠ") - varLineData.selfReplace("tie", "ㄊㄧㄝ") - varLineData.selfReplace("tou", "ㄊㄡ") - varLineData.selfReplace("tui", "ㄊㄨㄟ") - varLineData.selfReplace("tun", "ㄊㄨㄣ") - varLineData.selfReplace("tuo", "ㄊㄨㄛ") - varLineData.selfReplace("wai", "ㄨㄞ") - varLineData.selfReplace("wan", "ㄨㄢ") - varLineData.selfReplace("wei", "ㄨㄟ") - varLineData.selfReplace("wen", "ㄨㄣ") - varLineData.selfReplace("xia", "ㄒㄧㄚ") - varLineData.selfReplace("xie", "ㄒㄧㄝ") - varLineData.selfReplace("xin", "ㄒㄧㄣ") - varLineData.selfReplace("xiu", "ㄒㄧㄡ") - varLineData.selfReplace("xue", "ㄒㄩㄝ") - varLineData.selfReplace("xun", "ㄒㄩㄣ") - varLineData.selfReplace("yai", "ㄧㄞ") - varLineData.selfReplace("yan", "ㄧㄢ") - varLineData.selfReplace("yao", "ㄧㄠ") - varLineData.selfReplace("yin", "ㄧㄣ") - varLineData.selfReplace("you", "ㄧㄡ") - varLineData.selfReplace("yue", "ㄩㄝ") - varLineData.selfReplace("yun", "ㄩㄣ") - varLineData.selfReplace("zai", "ㄗㄞ") - varLineData.selfReplace("zan", "ㄗㄢ") - varLineData.selfReplace("zao", "ㄗㄠ") - varLineData.selfReplace("zei", "ㄗㄟ") - varLineData.selfReplace("zen", "ㄗㄣ") - varLineData.selfReplace("zha", "ㄓㄚ") - varLineData.selfReplace("zhe", "ㄓㄜ") - varLineData.selfReplace("zhi", "ㄓ") - varLineData.selfReplace("zhu", "ㄓㄨ") - varLineData.selfReplace("zou", "ㄗㄡ") - varLineData.selfReplace("zui", "ㄗㄨㄟ") - varLineData.selfReplace("zun", "ㄗㄨㄣ") - varLineData.selfReplace("zuo", "ㄗㄨㄛ") - varLineData.selfReplace("ai", "ㄞ") - varLineData.selfReplace("an", "ㄢ") - varLineData.selfReplace("ao", "ㄠ") - varLineData.selfReplace("ba", "ㄅㄚ") - varLineData.selfReplace("bi", "ㄅㄧ") - varLineData.selfReplace("bo", "ㄅㄛ") - varLineData.selfReplace("bu", "ㄅㄨ") - varLineData.selfReplace("ca", "ㄘㄚ") - varLineData.selfReplace("ce", "ㄘㄜ") - varLineData.selfReplace("ci", "ㄘ") - varLineData.selfReplace("cu", "ㄘㄨ") - varLineData.selfReplace("da", "ㄉㄚ") - varLineData.selfReplace("de", "ㄉㄜ") - varLineData.selfReplace("di", "ㄉㄧ") - varLineData.selfReplace("du", "ㄉㄨ") - varLineData.selfReplace("eh", "ㄝ") - varLineData.selfReplace("ei", "ㄟ") - varLineData.selfReplace("en", "ㄣ") - varLineData.selfReplace("er", "ㄦ") - varLineData.selfReplace("fa", "ㄈㄚ") - varLineData.selfReplace("fo", "ㄈㄛ") - varLineData.selfReplace("fu", "ㄈㄨ") - varLineData.selfReplace("ga", "ㄍㄚ") - varLineData.selfReplace("ge", "ㄍㄜ") - varLineData.selfReplace("gi", "ㄍㄧ") - varLineData.selfReplace("gu", "ㄍㄨ") - varLineData.selfReplace("ha", "ㄏㄚ") - varLineData.selfReplace("he", "ㄏㄜ") - varLineData.selfReplace("hu", "ㄏㄨ") - varLineData.selfReplace("ji", "ㄐㄧ") - varLineData.selfReplace("ju", "ㄐㄩ") - varLineData.selfReplace("ka", "ㄎㄚ") - varLineData.selfReplace("ke", "ㄎㄜ") - varLineData.selfReplace("ku", "ㄎㄨ") - varLineData.selfReplace("la", "ㄌㄚ") - varLineData.selfReplace("le", "ㄌㄜ") - varLineData.selfReplace("li", "ㄌㄧ") - varLineData.selfReplace("lo", "ㄌㄛ") - varLineData.selfReplace("lu", "ㄌㄨ") - varLineData.selfReplace("lv", "ㄌㄩ") - varLineData.selfReplace("ma", "ㄇㄚ") - varLineData.selfReplace("me", "ㄇㄜ") - varLineData.selfReplace("mi", "ㄇㄧ") - varLineData.selfReplace("mo", "ㄇㄛ") - varLineData.selfReplace("mu", "ㄇㄨ") - varLineData.selfReplace("na", "ㄋㄚ") - varLineData.selfReplace("ne", "ㄋㄜ") - varLineData.selfReplace("ni", "ㄋㄧ") - varLineData.selfReplace("nu", "ㄋㄨ") - varLineData.selfReplace("nv", "ㄋㄩ") - varLineData.selfReplace("ou", "ㄡ") - varLineData.selfReplace("pa", "ㄆㄚ") - varLineData.selfReplace("pi", "ㄆㄧ") - varLineData.selfReplace("po", "ㄆㄛ") - varLineData.selfReplace("pu", "ㄆㄨ") - varLineData.selfReplace("qi", "ㄑㄧ") - varLineData.selfReplace("qu", "ㄑㄩ") - varLineData.selfReplace("re", "ㄖㄜ") - varLineData.selfReplace("ri", "ㄖ") - varLineData.selfReplace("ru", "ㄖㄨ") - varLineData.selfReplace("sa", "ㄙㄚ") - varLineData.selfReplace("se", "ㄙㄜ") - varLineData.selfReplace("si", "ㄙ") - varLineData.selfReplace("su", "ㄙㄨ") - varLineData.selfReplace("ta", "ㄊㄚ") - varLineData.selfReplace("te", "ㄊㄜ") - varLineData.selfReplace("ti", "ㄊㄧ") - varLineData.selfReplace("tu", "ㄊㄨ") - varLineData.selfReplace("wa", "ㄨㄚ") - varLineData.selfReplace("wo", "ㄨㄛ") - varLineData.selfReplace("wu", "ㄨ") - varLineData.selfReplace("xi", "ㄒㄧ") - varLineData.selfReplace("xu", "ㄒㄩ") - varLineData.selfReplace("ya", "ㄧㄚ") - varLineData.selfReplace("ye", "ㄧㄝ") - varLineData.selfReplace("yi", "ㄧ") - varLineData.selfReplace("yo", "ㄧㄛ") - varLineData.selfReplace("yu", "ㄩ") - varLineData.selfReplace("za", "ㄗㄚ") - varLineData.selfReplace("ze", "ㄗㄜ") - varLineData.selfReplace("zi", "ㄗ") - varLineData.selfReplace("zu", "ㄗㄨ") - varLineData.selfReplace("a", "ㄚ") - varLineData.selfReplace("e", "ㄜ") - varLineData.selfReplace("o", "ㄛ") - varLineData.selfReplace("q", "ㄑ") - varLineData.selfReplace("2", "ˊ") - varLineData.selfReplace("3", "ˇ") - varLineData.selfReplace("4", "ˋ") - varLineData.selfReplace("5", "˙") - varLineData.selfReplace("1", "") - strProcessed += varLineData - strProcessed += "\n" - } - } - - // Step 3: Add Formatted Pragma - let hdrFormatted = "# 𝙵𝙾𝚁𝙼𝙰𝚃 𝚘𝚛𝚐.𝚊𝚝𝚎𝚕𝚒𝚎𝚛𝙸𝚗𝚖𝚞.𝚟𝚌𝚑𝚎𝚠𝚒𝚗𝚐.𝚞𝚜𝚎𝚛𝙻𝚊𝚗𝚐𝚞𝚊𝚐𝚎𝙼𝚘𝚍𝚎𝚕𝙳𝚊𝚝𝚊.𝚏𝚘𝚛𝚖𝚊𝚝𝚝𝚎𝚍\n" // Sorted Header - strProcessed = hdrFormatted + strProcessed // Add Sorted Header - - // Step 4: Deduplication. - arrData = strProcessed.components(separatedBy: "\n") - strProcessed = "" // Reset its value - // 下面兩行的 reversed 是首尾顛倒,免得破壞最新的 override 資訊。 - let arrDataDeduplicated = Array(NSOrderedSet(array: arrData.reversed()).array as! [String]) - for lineData in arrDataDeduplicated.reversed() { - strProcessed += lineData - strProcessed += "\n" - } - - // Step 5: Remove duplicated newlines at the end of the file. - strProcessed.regReplace(pattern: "\\n+", replaceWith: "\n") - - // Step 6: Commit Formatted Contents. - self = strProcessed - } + mutating func regReplace(pattern: String, replaceWith: String = "") { + // Ref: https://stackoverflow.com/a/40993403/4162914 && https://stackoverflow.com/a/71291137/4162914 + do { + let regex = try NSRegularExpression( + pattern: pattern, options: [.caseInsensitive, .anchorsMatchLines]) + let range = NSRange(self.startIndex..., in: self) + self = regex.stringByReplacingMatches( + in: self, options: [], range: range, withTemplate: replaceWith) + } catch { return } + } + mutating func selfReplace(_ strOf: String, _ strWith: String = "") { + self = self.replacingOccurrences(of: strOf, with: strWith) + } + mutating func formatConsolidate(cnvHYPYtoBPMF: Bool) { + // Step 1: Consolidating formats per line. + var strProcessed = self + // 預處理格式 + strProcessed = strProcessed.replacingOccurrences(of: " #MACOS", with: "") // 去掉 macOS 標記 + // CJKWhiteSpace (\x{3000}) to ASCII Space + // NonBreakWhiteSpace (\x{A0}) to ASCII Space + // Tab to ASCII Space + // 統整連續空格為一個 ASCII 空格 + strProcessed.regReplace(pattern: #"( +| +| +|\t+)+"#, replaceWith: " ") + strProcessed.regReplace(pattern: #"(^ | $)"#, replaceWith: "") // 去除行尾行首空格 + strProcessed.regReplace(pattern: #"(\f+|\r+|\n+)+"#, replaceWith: "\n") // CR & FF to LF, 且去除重複行 + if strProcessed.prefix(1) == " " { // 去除檔案開頭空格 + strProcessed.removeFirst() + } + if strProcessed.suffix(1) == " " { // 去除檔案結尾空格 + strProcessed.removeLast() + } + var arrData = [""] + if cnvHYPYtoBPMF { + // Step 0: Convert HanyuPinyin to Bopomofo. + arrData = strProcessed.components(separatedBy: "\n") + strProcessed = "" // Reset its value + for lineData in arrData { + var varLineData = lineData + // 漢語拼音轉注音,得先從最長的可能的拼音組合開始轉起, + // 這樣等轉換到更短的可能的漢語拼音組合時就不會出錯。 + // 依此類推,聲調放在最後來轉換。 + varLineData.selfReplace("chuang", "ㄔㄨㄤ") + varLineData.selfReplace("shuang", "ㄕㄨㄤ") + varLineData.selfReplace("zhuang", "ㄓㄨㄤ") + varLineData.selfReplace("chang", "ㄔㄤ") + varLineData.selfReplace("cheng", "ㄔㄥ") + varLineData.selfReplace("chong", "ㄔㄨㄥ") + varLineData.selfReplace("chuai", "ㄔㄨㄞ") + varLineData.selfReplace("chuan", "ㄔㄨㄢ") + varLineData.selfReplace("guang", "ㄍㄨㄤ") + varLineData.selfReplace("huang", "ㄏㄨㄤ") + varLineData.selfReplace("jiang", "ㄐㄧㄤ") + varLineData.selfReplace("jiong", "ㄐㄩㄥ") + varLineData.selfReplace("kuang", "ㄎㄨㄤ") + varLineData.selfReplace("liang", "ㄌㄧㄤ") + varLineData.selfReplace("niang", "ㄋㄧㄤ") + varLineData.selfReplace("qiang", "ㄑㄧㄤ") + varLineData.selfReplace("qiong", "ㄑㄩㄥ") + varLineData.selfReplace("shang", "ㄕㄤ") + varLineData.selfReplace("sheng", "ㄕㄥ") + varLineData.selfReplace("shuai", "ㄕㄨㄞ") + varLineData.selfReplace("shuan", "ㄕㄨㄢ") + varLineData.selfReplace("xiang", "ㄒㄧㄤ") + varLineData.selfReplace("xiong", "ㄒㄩㄥ") + varLineData.selfReplace("zhang", "ㄓㄤ") + varLineData.selfReplace("zheng", "ㄓㄥ") + varLineData.selfReplace("zhong", "ㄓㄨㄥ") + varLineData.selfReplace("zhuai", "ㄓㄨㄞ") + varLineData.selfReplace("zhuan", "ㄓㄨㄢ") + varLineData.selfReplace("bang", "ㄅㄤ") + varLineData.selfReplace("beng", "ㄅㄥ") + varLineData.selfReplace("bian", "ㄅㄧㄢ") + varLineData.selfReplace("biao", "ㄅㄧㄠ") + varLineData.selfReplace("bing", "ㄅㄧㄥ") + varLineData.selfReplace("cang", "ㄘㄤ") + varLineData.selfReplace("ceng", "ㄘㄥ") + varLineData.selfReplace("chai", "ㄔㄞ") + varLineData.selfReplace("chan", "ㄔㄢ") + varLineData.selfReplace("chao", "ㄔㄠ") + varLineData.selfReplace("chen", "ㄔㄣ") + varLineData.selfReplace("chou", "ㄔㄡ") + varLineData.selfReplace("chua", "ㄔㄨㄚ") + varLineData.selfReplace("chui", "ㄔㄨㄟ") + varLineData.selfReplace("chun", "ㄔㄨㄣ") + varLineData.selfReplace("chuo", "ㄔㄨㄛ") + varLineData.selfReplace("cong", "ㄘㄨㄥ") + varLineData.selfReplace("cuan", "ㄘㄨㄢ") + varLineData.selfReplace("dang", "ㄉㄤ") + varLineData.selfReplace("deng", "ㄉㄥ") + varLineData.selfReplace("dian", "ㄉㄧㄢ") + varLineData.selfReplace("diao", "ㄉㄧㄠ") + varLineData.selfReplace("ding", "ㄉㄧㄥ") + varLineData.selfReplace("dong", "ㄉㄨㄥ") + varLineData.selfReplace("duan", "ㄉㄨㄢ") + varLineData.selfReplace("fang", "ㄈㄤ") + varLineData.selfReplace("feng", "ㄈㄥ") + varLineData.selfReplace("fiao", "ㄈㄧㄠ") + varLineData.selfReplace("fong", "ㄈㄨㄥ") + varLineData.selfReplace("gang", "ㄍㄤ") + varLineData.selfReplace("geng", "ㄍㄥ") + varLineData.selfReplace("giao", "ㄍㄧㄠ") + varLineData.selfReplace("gong", "ㄍㄨㄥ") + varLineData.selfReplace("guai", "ㄍㄨㄞ") + varLineData.selfReplace("guan", "ㄍㄨㄢ") + varLineData.selfReplace("hang", "ㄏㄤ") + varLineData.selfReplace("heng", "ㄏㄥ") + varLineData.selfReplace("hong", "ㄏㄨㄥ") + varLineData.selfReplace("huai", "ㄏㄨㄞ") + varLineData.selfReplace("huan", "ㄏㄨㄢ") + varLineData.selfReplace("jian", "ㄐㄧㄢ") + varLineData.selfReplace("jiao", "ㄐㄧㄠ") + varLineData.selfReplace("jing", "ㄐㄧㄥ") + varLineData.selfReplace("juan", "ㄐㄩㄢ") + varLineData.selfReplace("kang", "ㄎㄤ") + varLineData.selfReplace("keng", "ㄎㄥ") + varLineData.selfReplace("kong", "ㄎㄨㄥ") + varLineData.selfReplace("kuai", "ㄎㄨㄞ") + varLineData.selfReplace("kuan", "ㄎㄨㄢ") + varLineData.selfReplace("lang", "ㄌㄤ") + varLineData.selfReplace("leng", "ㄌㄥ") + varLineData.selfReplace("lian", "ㄌㄧㄢ") + varLineData.selfReplace("liao", "ㄌㄧㄠ") + varLineData.selfReplace("ling", "ㄌㄧㄥ") + varLineData.selfReplace("long", "ㄌㄨㄥ") + varLineData.selfReplace("luan", "ㄌㄨㄢ") + varLineData.selfReplace("lvan", "ㄌㄩㄢ") + varLineData.selfReplace("mang", "ㄇㄤ") + varLineData.selfReplace("meng", "ㄇㄥ") + varLineData.selfReplace("mian", "ㄇㄧㄢ") + varLineData.selfReplace("miao", "ㄇㄧㄠ") + varLineData.selfReplace("ming", "ㄇㄧㄥ") + varLineData.selfReplace("nang", "ㄋㄤ") + varLineData.selfReplace("neng", "ㄋㄥ") + varLineData.selfReplace("nian", "ㄋㄧㄢ") + varLineData.selfReplace("niao", "ㄋㄧㄠ") + varLineData.selfReplace("ning", "ㄋㄧㄥ") + varLineData.selfReplace("nong", "ㄋㄨㄥ") + varLineData.selfReplace("nuan", "ㄋㄨㄢ") + varLineData.selfReplace("pang", "ㄆㄤ") + varLineData.selfReplace("peng", "ㄆㄥ") + varLineData.selfReplace("pian", "ㄆㄧㄢ") + varLineData.selfReplace("piao", "ㄆㄧㄠ") + varLineData.selfReplace("ping", "ㄆㄧㄥ") + varLineData.selfReplace("qian", "ㄑㄧㄢ") + varLineData.selfReplace("qiao", "ㄑㄧㄠ") + varLineData.selfReplace("qing", "ㄑㄧㄥ") + varLineData.selfReplace("quan", "ㄑㄩㄢ") + varLineData.selfReplace("rang", "ㄖㄤ") + varLineData.selfReplace("reng", "ㄖㄥ") + varLineData.selfReplace("rong", "ㄖㄨㄥ") + varLineData.selfReplace("ruan", "ㄖㄨㄢ") + varLineData.selfReplace("sang", "ㄙㄤ") + varLineData.selfReplace("seng", "ㄙㄥ") + varLineData.selfReplace("shai", "ㄕㄞ") + varLineData.selfReplace("shan", "ㄕㄢ") + varLineData.selfReplace("shao", "ㄕㄠ") + varLineData.selfReplace("shei", "ㄕㄟ") + varLineData.selfReplace("shen", "ㄕㄣ") + varLineData.selfReplace("shou", "ㄕㄡ") + varLineData.selfReplace("shua", "ㄕㄨㄚ") + varLineData.selfReplace("shui", "ㄕㄨㄟ") + varLineData.selfReplace("shun", "ㄕㄨㄣ") + varLineData.selfReplace("shuo", "ㄕㄨㄛ") + varLineData.selfReplace("song", "ㄙㄨㄥ") + varLineData.selfReplace("suan", "ㄙㄨㄢ") + varLineData.selfReplace("tang", "ㄊㄤ") + varLineData.selfReplace("teng", "ㄊㄥ") + varLineData.selfReplace("tian", "ㄊㄧㄢ") + varLineData.selfReplace("tiao", "ㄊㄧㄠ") + varLineData.selfReplace("ting", "ㄊㄧㄥ") + varLineData.selfReplace("tong", "ㄊㄨㄥ") + varLineData.selfReplace("tuan", "ㄊㄨㄢ") + varLineData.selfReplace("wang", "ㄨㄤ") + varLineData.selfReplace("weng", "ㄨㄥ") + varLineData.selfReplace("xian", "ㄒㄧㄢ") + varLineData.selfReplace("xiao", "ㄒㄧㄠ") + varLineData.selfReplace("xing", "ㄒㄧㄥ") + varLineData.selfReplace("xuan", "ㄒㄩㄢ") + varLineData.selfReplace("yang", "ㄧㄤ") + varLineData.selfReplace("ying", "ㄧㄥ") + varLineData.selfReplace("yong", "ㄩㄥ") + varLineData.selfReplace("yuan", "ㄩㄢ") + varLineData.selfReplace("zang", "ㄗㄤ") + varLineData.selfReplace("zeng", "ㄗㄥ") + varLineData.selfReplace("zhai", "ㄓㄞ") + varLineData.selfReplace("zhan", "ㄓㄢ") + varLineData.selfReplace("zhao", "ㄓㄠ") + varLineData.selfReplace("zhei", "ㄓㄟ") + varLineData.selfReplace("zhen", "ㄓㄣ") + varLineData.selfReplace("zhou", "ㄓㄡ") + varLineData.selfReplace("zhua", "ㄓㄨㄚ") + varLineData.selfReplace("zhui", "ㄓㄨㄟ") + varLineData.selfReplace("zhun", "ㄓㄨㄣ") + varLineData.selfReplace("zhuo", "ㄓㄨㄛ") + varLineData.selfReplace("zong", "ㄗㄨㄥ") + varLineData.selfReplace("zuan", "ㄗㄨㄢ") + varLineData.selfReplace("jun", "ㄐㄩㄣ") + varLineData.selfReplace("ang", "ㄤ") + varLineData.selfReplace("bai", "ㄅㄞ") + varLineData.selfReplace("ban", "ㄅㄢ") + varLineData.selfReplace("bao", "ㄅㄠ") + varLineData.selfReplace("bei", "ㄅㄟ") + varLineData.selfReplace("ben", "ㄅㄣ") + varLineData.selfReplace("bie", "ㄅㄧㄝ") + varLineData.selfReplace("bin", "ㄅㄧㄣ") + varLineData.selfReplace("cai", "ㄘㄞ") + varLineData.selfReplace("can", "ㄘㄢ") + varLineData.selfReplace("cao", "ㄘㄠ") + varLineData.selfReplace("cei", "ㄘㄟ") + varLineData.selfReplace("cen", "ㄘㄣ") + varLineData.selfReplace("cha", "ㄔㄚ") + varLineData.selfReplace("che", "ㄔㄜ") + varLineData.selfReplace("chi", "ㄔ") + varLineData.selfReplace("chu", "ㄔㄨ") + varLineData.selfReplace("cou", "ㄘㄡ") + varLineData.selfReplace("cui", "ㄘㄨㄟ") + varLineData.selfReplace("cun", "ㄘㄨㄣ") + varLineData.selfReplace("cuo", "ㄘㄨㄛ") + varLineData.selfReplace("dai", "ㄉㄞ") + varLineData.selfReplace("dan", "ㄉㄢ") + varLineData.selfReplace("dao", "ㄉㄠ") + varLineData.selfReplace("dei", "ㄉㄟ") + varLineData.selfReplace("den", "ㄉㄣ") + varLineData.selfReplace("dia", "ㄉㄧㄚ") + varLineData.selfReplace("die", "ㄉㄧㄝ") + varLineData.selfReplace("diu", "ㄉㄧㄡ") + varLineData.selfReplace("dou", "ㄉㄡ") + varLineData.selfReplace("dui", "ㄉㄨㄟ") + varLineData.selfReplace("dun", "ㄉㄨㄣ") + varLineData.selfReplace("duo", "ㄉㄨㄛ") + varLineData.selfReplace("eng", "ㄥ") + varLineData.selfReplace("fan", "ㄈㄢ") + varLineData.selfReplace("fei", "ㄈㄟ") + varLineData.selfReplace("fen", "ㄈㄣ") + varLineData.selfReplace("fou", "ㄈㄡ") + varLineData.selfReplace("gai", "ㄍㄞ") + varLineData.selfReplace("gan", "ㄍㄢ") + varLineData.selfReplace("gao", "ㄍㄠ") + varLineData.selfReplace("gei", "ㄍㄟ") + varLineData.selfReplace("gin", "ㄍㄧㄣ") + varLineData.selfReplace("gen", "ㄍㄣ") + varLineData.selfReplace("gou", "ㄍㄡ") + varLineData.selfReplace("gua", "ㄍㄨㄚ") + varLineData.selfReplace("gue", "ㄍㄨㄜ") + varLineData.selfReplace("gui", "ㄍㄨㄟ") + varLineData.selfReplace("gun", "ㄍㄨㄣ") + varLineData.selfReplace("guo", "ㄍㄨㄛ") + varLineData.selfReplace("hai", "ㄏㄞ") + varLineData.selfReplace("han", "ㄏㄢ") + varLineData.selfReplace("hao", "ㄏㄠ") + varLineData.selfReplace("hei", "ㄏㄟ") + varLineData.selfReplace("hen", "ㄏㄣ") + varLineData.selfReplace("hou", "ㄏㄡ") + varLineData.selfReplace("hua", "ㄏㄨㄚ") + varLineData.selfReplace("hui", "ㄏㄨㄟ") + varLineData.selfReplace("hun", "ㄏㄨㄣ") + varLineData.selfReplace("huo", "ㄏㄨㄛ") + varLineData.selfReplace("jia", "ㄐㄧㄚ") + varLineData.selfReplace("jie", "ㄐㄧㄝ") + varLineData.selfReplace("jin", "ㄐㄧㄣ") + varLineData.selfReplace("jiu", "ㄐㄧㄡ") + varLineData.selfReplace("jue", "ㄐㄩㄝ") + varLineData.selfReplace("kai", "ㄎㄞ") + varLineData.selfReplace("kan", "ㄎㄢ") + varLineData.selfReplace("kao", "ㄎㄠ") + varLineData.selfReplace("ken", "ㄎㄣ") + varLineData.selfReplace("kiu", "ㄎㄧㄡ") + varLineData.selfReplace("kou", "ㄎㄡ") + varLineData.selfReplace("kua", "ㄎㄨㄚ") + varLineData.selfReplace("kui", "ㄎㄨㄟ") + varLineData.selfReplace("kun", "ㄎㄨㄣ") + varLineData.selfReplace("kuo", "ㄎㄨㄛ") + varLineData.selfReplace("lai", "ㄌㄞ") + varLineData.selfReplace("lan", "ㄌㄢ") + varLineData.selfReplace("lao", "ㄌㄠ") + varLineData.selfReplace("lei", "ㄌㄟ") + varLineData.selfReplace("lia", "ㄌㄧㄚ") + varLineData.selfReplace("lie", "ㄌㄧㄝ") + varLineData.selfReplace("lin", "ㄌㄧㄣ") + varLineData.selfReplace("liu", "ㄌㄧㄡ") + varLineData.selfReplace("lou", "ㄌㄡ") + varLineData.selfReplace("lun", "ㄌㄨㄣ") + varLineData.selfReplace("luo", "ㄌㄨㄛ") + varLineData.selfReplace("lve", "ㄌㄩㄝ") + varLineData.selfReplace("mai", "ㄇㄞ") + varLineData.selfReplace("man", "ㄇㄢ") + varLineData.selfReplace("mao", "ㄇㄠ") + varLineData.selfReplace("mei", "ㄇㄟ") + varLineData.selfReplace("men", "ㄇㄣ") + varLineData.selfReplace("mie", "ㄇㄧㄝ") + varLineData.selfReplace("min", "ㄇㄧㄣ") + varLineData.selfReplace("miu", "ㄇㄧㄡ") + varLineData.selfReplace("mou", "ㄇㄡ") + varLineData.selfReplace("nai", "ㄋㄞ") + varLineData.selfReplace("nan", "ㄋㄢ") + varLineData.selfReplace("nao", "ㄋㄠ") + varLineData.selfReplace("nei", "ㄋㄟ") + varLineData.selfReplace("nen", "ㄋㄣ") + varLineData.selfReplace("nie", "ㄋㄧㄝ") + varLineData.selfReplace("nin", "ㄋㄧㄣ") + varLineData.selfReplace("niu", "ㄋㄧㄡ") + varLineData.selfReplace("nou", "ㄋㄡ") + varLineData.selfReplace("nui", "ㄋㄨㄟ") + varLineData.selfReplace("nun", "ㄋㄨㄣ") + varLineData.selfReplace("nuo", "ㄋㄨㄛ") + varLineData.selfReplace("nve", "ㄋㄩㄝ") + varLineData.selfReplace("pai", "ㄆㄞ") + varLineData.selfReplace("pan", "ㄆㄢ") + varLineData.selfReplace("pao", "ㄆㄠ") + varLineData.selfReplace("pei", "ㄆㄟ") + varLineData.selfReplace("pen", "ㄆㄣ") + varLineData.selfReplace("pia", "ㄆㄧㄚ") + varLineData.selfReplace("pie", "ㄆㄧㄝ") + varLineData.selfReplace("pin", "ㄆㄧㄣ") + varLineData.selfReplace("pou", "ㄆㄡ") + varLineData.selfReplace("qia", "ㄑㄧㄚ") + varLineData.selfReplace("qie", "ㄑㄧㄝ") + varLineData.selfReplace("qin", "ㄑㄧㄣ") + varLineData.selfReplace("qiu", "ㄑㄧㄡ") + varLineData.selfReplace("que", "ㄑㄩㄝ") + varLineData.selfReplace("qun", "ㄑㄩㄣ") + varLineData.selfReplace("ran", "ㄖㄢ") + varLineData.selfReplace("rao", "ㄖㄠ") + varLineData.selfReplace("ren", "ㄖㄣ") + varLineData.selfReplace("rou", "ㄖㄡ") + varLineData.selfReplace("rui", "ㄖㄨㄟ") + varLineData.selfReplace("run", "ㄖㄨㄣ") + varLineData.selfReplace("ruo", "ㄖㄨㄛ") + varLineData.selfReplace("sai", "ㄙㄞ") + varLineData.selfReplace("san", "ㄙㄢ") + varLineData.selfReplace("sao", "ㄙㄠ") + varLineData.selfReplace("sei", "ㄙㄟ") + varLineData.selfReplace("sen", "ㄙㄣ") + varLineData.selfReplace("sha", "ㄕㄚ") + varLineData.selfReplace("she", "ㄕㄜ") + varLineData.selfReplace("shi", "ㄕ") + varLineData.selfReplace("shu", "ㄕㄨ") + varLineData.selfReplace("sou", "ㄙㄡ") + varLineData.selfReplace("sui", "ㄙㄨㄟ") + varLineData.selfReplace("sun", "ㄙㄨㄣ") + varLineData.selfReplace("suo", "ㄙㄨㄛ") + varLineData.selfReplace("tai", "ㄊㄞ") + varLineData.selfReplace("tan", "ㄊㄢ") + varLineData.selfReplace("tao", "ㄊㄠ") + varLineData.selfReplace("tie", "ㄊㄧㄝ") + varLineData.selfReplace("tou", "ㄊㄡ") + varLineData.selfReplace("tui", "ㄊㄨㄟ") + varLineData.selfReplace("tun", "ㄊㄨㄣ") + varLineData.selfReplace("tuo", "ㄊㄨㄛ") + varLineData.selfReplace("wai", "ㄨㄞ") + varLineData.selfReplace("wan", "ㄨㄢ") + varLineData.selfReplace("wei", "ㄨㄟ") + varLineData.selfReplace("wen", "ㄨㄣ") + varLineData.selfReplace("xia", "ㄒㄧㄚ") + varLineData.selfReplace("xie", "ㄒㄧㄝ") + varLineData.selfReplace("xin", "ㄒㄧㄣ") + varLineData.selfReplace("xiu", "ㄒㄧㄡ") + varLineData.selfReplace("xue", "ㄒㄩㄝ") + varLineData.selfReplace("xun", "ㄒㄩㄣ") + varLineData.selfReplace("yai", "ㄧㄞ") + varLineData.selfReplace("yan", "ㄧㄢ") + varLineData.selfReplace("yao", "ㄧㄠ") + varLineData.selfReplace("yin", "ㄧㄣ") + varLineData.selfReplace("you", "ㄧㄡ") + varLineData.selfReplace("yue", "ㄩㄝ") + varLineData.selfReplace("yun", "ㄩㄣ") + varLineData.selfReplace("zai", "ㄗㄞ") + varLineData.selfReplace("zan", "ㄗㄢ") + varLineData.selfReplace("zao", "ㄗㄠ") + varLineData.selfReplace("zei", "ㄗㄟ") + varLineData.selfReplace("zen", "ㄗㄣ") + varLineData.selfReplace("zha", "ㄓㄚ") + varLineData.selfReplace("zhe", "ㄓㄜ") + varLineData.selfReplace("zhi", "ㄓ") + varLineData.selfReplace("zhu", "ㄓㄨ") + varLineData.selfReplace("zou", "ㄗㄡ") + varLineData.selfReplace("zui", "ㄗㄨㄟ") + varLineData.selfReplace("zun", "ㄗㄨㄣ") + varLineData.selfReplace("zuo", "ㄗㄨㄛ") + varLineData.selfReplace("ai", "ㄞ") + varLineData.selfReplace("an", "ㄢ") + varLineData.selfReplace("ao", "ㄠ") + varLineData.selfReplace("ba", "ㄅㄚ") + varLineData.selfReplace("bi", "ㄅㄧ") + varLineData.selfReplace("bo", "ㄅㄛ") + varLineData.selfReplace("bu", "ㄅㄨ") + varLineData.selfReplace("ca", "ㄘㄚ") + varLineData.selfReplace("ce", "ㄘㄜ") + varLineData.selfReplace("ci", "ㄘ") + varLineData.selfReplace("cu", "ㄘㄨ") + varLineData.selfReplace("da", "ㄉㄚ") + varLineData.selfReplace("de", "ㄉㄜ") + varLineData.selfReplace("di", "ㄉㄧ") + varLineData.selfReplace("du", "ㄉㄨ") + varLineData.selfReplace("eh", "ㄝ") + varLineData.selfReplace("ei", "ㄟ") + varLineData.selfReplace("en", "ㄣ") + varLineData.selfReplace("er", "ㄦ") + varLineData.selfReplace("fa", "ㄈㄚ") + varLineData.selfReplace("fo", "ㄈㄛ") + varLineData.selfReplace("fu", "ㄈㄨ") + varLineData.selfReplace("ga", "ㄍㄚ") + varLineData.selfReplace("ge", "ㄍㄜ") + varLineData.selfReplace("gi", "ㄍㄧ") + varLineData.selfReplace("gu", "ㄍㄨ") + varLineData.selfReplace("ha", "ㄏㄚ") + varLineData.selfReplace("he", "ㄏㄜ") + varLineData.selfReplace("hu", "ㄏㄨ") + varLineData.selfReplace("ji", "ㄐㄧ") + varLineData.selfReplace("ju", "ㄐㄩ") + varLineData.selfReplace("ka", "ㄎㄚ") + varLineData.selfReplace("ke", "ㄎㄜ") + varLineData.selfReplace("ku", "ㄎㄨ") + varLineData.selfReplace("la", "ㄌㄚ") + varLineData.selfReplace("le", "ㄌㄜ") + varLineData.selfReplace("li", "ㄌㄧ") + varLineData.selfReplace("lo", "ㄌㄛ") + varLineData.selfReplace("lu", "ㄌㄨ") + varLineData.selfReplace("lv", "ㄌㄩ") + varLineData.selfReplace("ma", "ㄇㄚ") + varLineData.selfReplace("me", "ㄇㄜ") + varLineData.selfReplace("mi", "ㄇㄧ") + varLineData.selfReplace("mo", "ㄇㄛ") + varLineData.selfReplace("mu", "ㄇㄨ") + varLineData.selfReplace("na", "ㄋㄚ") + varLineData.selfReplace("ne", "ㄋㄜ") + varLineData.selfReplace("ni", "ㄋㄧ") + varLineData.selfReplace("nu", "ㄋㄨ") + varLineData.selfReplace("nv", "ㄋㄩ") + varLineData.selfReplace("ou", "ㄡ") + varLineData.selfReplace("pa", "ㄆㄚ") + varLineData.selfReplace("pi", "ㄆㄧ") + varLineData.selfReplace("po", "ㄆㄛ") + varLineData.selfReplace("pu", "ㄆㄨ") + varLineData.selfReplace("qi", "ㄑㄧ") + varLineData.selfReplace("qu", "ㄑㄩ") + varLineData.selfReplace("re", "ㄖㄜ") + varLineData.selfReplace("ri", "ㄖ") + varLineData.selfReplace("ru", "ㄖㄨ") + varLineData.selfReplace("sa", "ㄙㄚ") + varLineData.selfReplace("se", "ㄙㄜ") + varLineData.selfReplace("si", "ㄙ") + varLineData.selfReplace("su", "ㄙㄨ") + varLineData.selfReplace("ta", "ㄊㄚ") + varLineData.selfReplace("te", "ㄊㄜ") + varLineData.selfReplace("ti", "ㄊㄧ") + varLineData.selfReplace("tu", "ㄊㄨ") + varLineData.selfReplace("wa", "ㄨㄚ") + varLineData.selfReplace("wo", "ㄨㄛ") + varLineData.selfReplace("wu", "ㄨ") + varLineData.selfReplace("xi", "ㄒㄧ") + varLineData.selfReplace("xu", "ㄒㄩ") + varLineData.selfReplace("ya", "ㄧㄚ") + varLineData.selfReplace("ye", "ㄧㄝ") + varLineData.selfReplace("yi", "ㄧ") + varLineData.selfReplace("yo", "ㄧㄛ") + varLineData.selfReplace("yu", "ㄩ") + varLineData.selfReplace("za", "ㄗㄚ") + varLineData.selfReplace("ze", "ㄗㄜ") + varLineData.selfReplace("zi", "ㄗ") + varLineData.selfReplace("zu", "ㄗㄨ") + varLineData.selfReplace("a", "ㄚ") + varLineData.selfReplace("e", "ㄜ") + varLineData.selfReplace("o", "ㄛ") + varLineData.selfReplace("q", "ㄑ") + varLineData.selfReplace("2", "ˊ") + varLineData.selfReplace("3", "ˇ") + varLineData.selfReplace("4", "ˋ") + varLineData.selfReplace("5", "˙") + varLineData.selfReplace("1", "") + strProcessed += varLineData + strProcessed += "\n" + } + } + + // Step 3: Add Formatted Pragma, the Sorted Header: + let hdrFormatted = "# 𝙵𝙾𝚁𝙼𝙰𝚃 𝚘𝚛𝚐.𝚊𝚝𝚎𝚕𝚒𝚎𝚛𝙸𝚗𝚖𝚞.𝚟𝚌𝚑𝚎𝚠𝚒𝚗𝚐.𝚞𝚜𝚎𝚛𝙻𝚊𝚗𝚐𝚞𝚊𝚐𝚎𝙼𝚘𝚍𝚎𝚕𝙳𝚊𝚝𝚊.𝚏𝚘𝚛𝚖𝚊𝚝𝚝𝚎𝚍\n" + strProcessed = hdrFormatted + strProcessed // Add Sorted Header + + // Step 4: Deduplication. + arrData = strProcessed.components(separatedBy: "\n") + strProcessed = "" // Reset its value + // 下面兩行的 reversed 是首尾顛倒,免得破壞最新的 override 資訊。 + let arrDataDeduplicated = Array(NSOrderedSet(array: arrData.reversed()).array as! [String]) + for lineData in arrDataDeduplicated.reversed() { + strProcessed += lineData + strProcessed += "\n" + } + + // Step 5: Remove duplicated newlines at the end of the file. + strProcessed.regReplace(pattern: "\\n+", replaceWith: "\n") + + // Step 6: Commit Formatted Contents. + self = strProcessed + } } diff --git a/UserPhraseEditor/ViewController.swift b/UserPhraseEditor/ViewController.swift index 20831686..84c8e345 100644 --- a/UserPhraseEditor/ViewController.swift +++ b/UserPhraseEditor/ViewController.swift @@ -1,59 +1,65 @@ // Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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 class ViewController: NSViewController, NSTextViewDelegate { - /// - Tag: setRepresentedObjectExample - override var representedObject: Any? { - didSet { - // Pass down the represented object to all of the child view controllers. - for child in children { - child.representedObject = representedObject - } - } - } + /// - Tag: setRepresentedObjectExample + override var representedObject: Any? { + didSet { + // Pass down the represented object to all of the child view controllers. + for child in children { + child.representedObject = representedObject + } + } + } - weak var document: Document? { - if let docRepresentedObject = representedObject as? Document { - return docRepresentedObject - } - return nil - } + weak var document: Document? { + if let docRepresentedObject = representedObject as? Document { + return docRepresentedObject + } + return nil + } - override func viewDidLoad() { - super.viewDidLoad() - // Do any additional setup after loading the view. - } + override func viewDidLoad() { + super.viewDidLoad() + // Do any additional setup after loading the view. + } - override func viewDidAppear() { - super.viewDidAppear() - } + override func viewDidAppear() { + super.viewDidAppear() + } - // MARK: - NSTextViewDelegate + // MARK: - NSTextViewDelegate - func textDidBeginEditing(_ notification: Notification) { - document?.objectDidBeginEditing(self) - } + func textDidBeginEditing(_ notification: Notification) { + document?.objectDidBeginEditing(self) + } - func textDidEndEditing(_ notification: Notification) { - document?.objectDidEndEditing(self) - } + func textDidEndEditing(_ notification: Notification) { + document?.objectDidEndEditing(self) + } } diff --git a/UserPhraseEditor/WindowController.swift b/UserPhraseEditor/WindowController.swift index 3d101202..12a8b6cb 100644 --- a/UserPhraseEditor/WindowController.swift +++ b/UserPhraseEditor/WindowController.swift @@ -1,35 +1,41 @@ // Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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 class WindowController: NSWindowController, NSWindowDelegate { - - override func windowDidLoad() { - super.windowDidLoad() - } - - required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - /** NSWindows loaded from the storyboard will be cascaded + + override func windowDidLoad() { + super.windowDidLoad() + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + /** NSWindows loaded from the storyboard will be cascaded based on the original frame of the window in the storyboard. */ - shouldCascadeWindows = true - } - + shouldCascadeWindows = true + } + } diff --git a/UserPhraseEditor/ctlAboutWindow.swift b/UserPhraseEditor/ctlAboutWindow.swift index c144e201..e38945b5 100644 --- a/UserPhraseEditor/ctlAboutWindow.swift +++ b/UserPhraseEditor/ctlAboutWindow.swift @@ -1,51 +1,64 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ import Cocoa @objc(AboutWindow) class ctlAboutWindow: NSWindowController { - @IBOutlet weak var appVersionLabel: NSTextField! - @IBOutlet weak var appCopyrightLabel: NSTextField! - @IBOutlet var appEULAContent: NSTextView! + @IBOutlet weak var appVersionLabel: NSTextField! + @IBOutlet weak var appCopyrightLabel: NSTextField! + @IBOutlet var appEULAContent: NSTextView! - override func windowDidLoad() { - super.windowDidLoad() - - window?.standardWindowButton(.closeButton)?.isHidden = true - window?.standardWindowButton(.miniaturizeButton)?.isHidden = true - window?.standardWindowButton(.zoomButton)?.isHidden = true - guard let installingVersion = Bundle.main.infoDictionary?[kCFBundleVersionKey as String] as? String, - let versionString = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String else { - return - } - if let copyrightLabel = Bundle.main.localizedInfoDictionary?["NSHumanReadableCopyright"] as? String { - appCopyrightLabel.stringValue = copyrightLabel - } - if let eulaContent = Bundle.main.localizedInfoDictionary?["CFEULAContent"] as? String { - appEULAContent.string = eulaContent - } - appVersionLabel.stringValue = String(format: "%@ Build %@", versionString, installingVersion) - } + override func windowDidLoad() { + super.windowDidLoad() - @IBAction func btnWiki(_ sender: NSButton) { - if let url = URL(string: "https://gitee.com/vchewing/vChewing-macOS/wikis") { - NSWorkspace.shared.open(url) - } - } + window?.standardWindowButton(.closeButton)?.isHidden = true + window?.standardWindowButton(.miniaturizeButton)?.isHidden = true + window?.standardWindowButton(.zoomButton)?.isHidden = true + guard + let installingVersion = Bundle.main.infoDictionary?[kCFBundleVersionKey as String] + as? String, + let versionString = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String + else { + return + } + if let copyrightLabel = Bundle.main.localizedInfoDictionary?["NSHumanReadableCopyright"] + as? String + { + appCopyrightLabel.stringValue = copyrightLabel + } + if let eulaContent = Bundle.main.localizedInfoDictionary?["CFEULAContent"] as? String { + appEULAContent.string = eulaContent + } + appVersionLabel.stringValue = String( + format: "%@ Build %@", versionString, installingVersion) + } + + @IBAction func btnWiki(_ sender: NSButton) { + if let url = URL(string: "https://gitee.com/vchewing/vChewing-macOS/wikis") { + NSWorkspace.shared.open(url) + } + } } From 75a3f308bb7ffdcac45826e86e03c5b66411f1dc Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sun, 3 Apr 2022 12:38:51 +0800 Subject: [PATCH 07/16] KeyHandler & mgrPrefs // Aftermath of Swift Clang-Format. --- .../Modules/ControllerModules/KeyHandler.mm | 18 +++++----- Source/Modules/IMEModules/mgrPrefs.swift | 34 +++++++++---------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/Source/Modules/ControllerModules/KeyHandler.mm b/Source/Modules/ControllerModules/KeyHandler.mm index 56dd029c..0509a9cb 100644 --- a/Source/Modules/ControllerModules/KeyHandler.mm +++ b/Source/Modules/ControllerModules/KeyHandler.mm @@ -176,33 +176,33 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; { NSInteger layout = mgrPrefs.keyboardLayout; switch (layout) { - case KeyboardLayoutStandard: + case KeyboardLayoutOfStandard: _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::StandardLayout()); break; - case KeyboardLayoutEten: + case KeyboardLayoutOfEten: _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::ETenLayout()); break; - case KeyboardLayoutHsu: + case KeyboardLayoutOfHsu: _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::HsuLayout()); break; - case KeyboardLayoutEten26: + case KeyboardLayoutOfEen26: _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::ETen26Layout()); break; - case KeyboardLayoutIBM: + case KeyboardLayoutOfIBM: _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::IBMLayout()); break; - case KeyboardLayoutMiTAC: + case KeyboardLayoutOfMiTAC: _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::MiTACLayout()); break; - case KeyboardLayoutFakeSeigyou: + case KeyboardLayoutOfFakeSeigyou: _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::FakeSeigyouLayout()); break; - case KeyboardLayoutHanyuPinyin: + case KeyboardLayoutOfHanyuPinyin: _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::HanyuPinyinLayout()); break; default: _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::StandardLayout()); - mgrPrefs.keyboardLayout = KeyboardLayoutStandard; + mgrPrefs.keyboardLayout = KeyboardLayoutOfStandard; } } diff --git a/Source/Modules/IMEModules/mgrPrefs.swift b/Source/Modules/IMEModules/mgrPrefs.swift index 1ab95969..d1f0a82a 100644 --- a/Source/Modules/IMEModules/mgrPrefs.swift +++ b/Source/Modules/IMEModules/mgrPrefs.swift @@ -156,32 +156,32 @@ struct ComposingBufferSize { // MARK: - @objc enum KeyboardLayout: Int { - case standard = 0 - case eten = 1 - case hsu = 2 - case eten26 = 3 - case ibm = 4 - case mitac = 5 - case fakeSeigyou = 6 - case hanyuPinyin = 10 + case ofStandard = 0 + case ofEten = 1 + case ofHsu = 2 + case ofEen26 = 3 + case ofIBM = 4 + case ofMiTAC = 5 + case ofFakeSeigyou = 6 + case ofHanyuPinyin = 10 var name: String { switch self { - case .standard: + case .ofStandard: return "Standard" - case .eten: + case .ofEten: return "ETen" - case .hsu: + case .ofHsu: return "Hsu" - case .eten26: + case .ofEen26: return "ETen26" - case .ibm: + case .ofIBM: return "IBM" - case .mitac: + case .ofMiTAC: return "MiTAC" - case .fakeSeigyou: + case .ofFakeSeigyou: return "FakeSeigyou" - case .hanyuPinyin: + case .ofHanyuPinyin: return "HanyuPinyin" } } @@ -360,7 +360,7 @@ struct ComposingBufferSize { @objc static var keyboardLayout: Int @objc static var keyboardLayoutName: String { - (KeyboardLayout(rawValue: self.keyboardLayout) ?? KeyboardLayout.standard).name + (KeyboardLayout(rawValue: self.keyboardLayout) ?? KeyboardLayout.ofStandard).name } @UserDefault( From 884b5cccc17d134b0ad89391bcd4231a8ef15a0f Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sun, 3 Apr 2022 14:05:04 +0800 Subject: [PATCH 08/16] (Obj)C(pp) // Clang-Format. --- .clang-format | 171 +++ Installer/Chronosphere.h | 31 +- Installer/Chronosphere.m | 40 +- Installer/Installer-Prefix.pch | 31 +- Installer/vChewingInstaller-Bridging-Header.h | 31 +- Source/3rdParty/OVMandarin/Mandarin.cpp | 1095 ++++++++------- Source/3rdParty/OVMandarin/Mandarin.h | 632 +++++---- Source/Headers/vChewing-Bridging-Header.h | 31 +- Source/Headers/vChewing-Prefix.pch | 31 +- Source/Modules/ControllerModules/KeyHandler.h | 42 +- .../Modules/ControllerModules/KeyHandler.mm | 1172 +++++++++++------ .../ControllerModules/KeyValueBlobReader.cpp | 97 +- .../ControllerModules/KeyValueBlobReader.h | 73 +- Source/Modules/FileHandlers/LMConsolidator.h | 43 +- Source/Modules/FileHandlers/LMConsolidator.mm | 153 ++- .../Modules/LangModelRelated/LMInstantiator.h | 80 +- .../LangModelRelated/LMInstantiator.mm | 143 +- .../SubLanguageModels/AssociatedPhrases.h | 55 +- .../SubLanguageModels/AssociatedPhrases.mm | 89 +- .../SubLanguageModels/CoreLM.h | 56 +- .../SubLanguageModels/CoreLM.mm | 178 ++- .../InstantiatedModels/CNSLM.h | 54 +- .../InstantiatedModels/SymbolLM.h | 54 +- .../InstantiatedModels/UserSymbolLM.h | 54 +- .../SubLanguageModels/ParselessLM.cpp | 96 +- .../SubLanguageModels/ParselessLM.h | 54 +- .../SubLanguageModels/ParselessPhraseDB.cpp | 111 +- .../SubLanguageModels/ParselessPhraseDB.h | 52 +- .../SubLanguageModels/PhraseReplacementMap.h | 46 +- .../SubLanguageModels/PhraseReplacementMap.mm | 80 +- .../SubLanguageModels/UserOverrideModel.cpp | 187 +-- .../SubLanguageModels/UserOverrideModel.h | 68 +- .../SubLanguageModels/UserPhrasesLM.h | 70 +- .../SubLanguageModels/UserPhrasesLM.mm | 108 +- .../Modules/LangModelRelated/mgrLangModel.h | 42 +- .../Modules/LangModelRelated/mgrLangModel.mm | 267 ++-- .../LangModelRelated/mgrLangModel_Privates.h | 43 +- .../LanguageParsers/Gramambular/Bigram.h | 104 +- .../Gramambular/BlockReadingBuilder.h | 215 +-- .../LanguageParsers/Gramambular/Gramambular.h | 31 +- .../LanguageParsers/Gramambular/Grid.h | 289 ++-- .../Gramambular/KeyValuePair.h | 65 +- .../Gramambular/LanguageModel.h | 55 +- .../LanguageParsers/Gramambular/Node.h | 240 ++-- .../LanguageParsers/Gramambular/NodeAnchor.h | 66 +- .../LanguageParsers/Gramambular/Span.h | 100 +- .../LanguageParsers/Gramambular/Unigram.h | 94 +- .../LanguageParsers/Gramambular/Walker.h | 106 +- 48 files changed, 4318 insertions(+), 2707 deletions(-) create mode 100644 .clang-format diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..41173776 --- /dev/null +++ b/.clang-format @@ -0,0 +1,171 @@ +--- +Language: Cpp +# BasedOnStyle: Microsoft +AccessModifierOffset: -1 +AlignAfterOpenBracket: Align +AlignConsecutiveMacros: false +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllConstructorInitializersOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortLambdasOnASingleLine: All +AllowShortIfStatementsOnASingleLine: WithoutElse +AllowShortLoopsOnASingleLine: true +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DeriveLineEnding: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Regroup +IncludeCategories: + - Regex: '^' + Priority: 2 + SortPriority: 0 + - Regex: '^<.*\.h>' + Priority: 1 + SortPriority: 0 + - Regex: '^<.*' + Priority: 2 + SortPriority: 0 + - Regex: '.*' + Priority: 3 + SortPriority: 0 +IncludeIsMainRegex: '([-_](test|unittest))?$' +IncludeIsMainSourceRegex: '' +IndentCaseLabels: true +IndentGotoLabels: true +IndentPPDirectives: None +IndentWidth: 4 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Never +ObjCBlockIndentWidth: 4 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 200 +PointerAlignment: Left +RawStringFormats: + - Language: Cpp + Delimiters: + - h + - m + - hh + - mm + - cc + - CC + - cpp + - Cpp + - CPP + - 'c++' + - 'C++' + CanonicalDelimiter: '' + BasedOnStyle: Microsoft + - Language: TextProto + Delimiters: + - pb + - PB + - proto + - PROTO + EnclosingFunctions: + - EqualsProto + - EquivToProto + - PARSE_PARTIAL_TEXT_PROTO + - PARSE_TEST_PROTO + - PARSE_TEXT_PROTO + - ParseTextOrDie + - ParseTextProtoOrDie + CanonicalDelimiter: '' + BasedOnStyle: Microsoft +ReflowComments: true +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInConditionalStatement: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +SpaceBeforeSquareBrackets: false +Standard: Auto +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 4 +UseCRLF: false +UseTab: Always +... diff --git a/Installer/Chronosphere.h b/Installer/Chronosphere.h index b1021f22..4f2cc7a0 100644 --- a/Installer/Chronosphere.h +++ b/Installer/Chronosphere.h @@ -1,20 +1,27 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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; diff --git a/Installer/Chronosphere.m b/Installer/Chronosphere.m index 7077b2b6..12ba786d 100644 --- a/Installer/Chronosphere.m +++ b/Installer/Chronosphere.m @@ -1,20 +1,27 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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 "Chronosphere.h" @@ -27,15 +34,18 @@ BOOL appBundleChronoshiftedToARandomizedPath(NSString *bundle) int entrySize = sizeof(struct statfs); struct statfs *bufs = (struct statfs *)calloc(entryCount, entrySize); entryCount = getfsstat(bufs, entryCount * entrySize, MNT_NOWAIT); - for (int i = 0; i < entryCount; i++) { - if (!strcmp(bundleAbsPath, bufs[i].f_mntfromname)) { + for (int i = 0; i < entryCount; i++) + { + if (!strcmp(bundleAbsPath, bufs[i].f_mntfromname)) + { free(bufs); // getfsstat() may return us a cached result, and so we need to get the stat of the mounted fs. // If statfs() returns an error, the mounted fs is already gone. struct statfs stat; int checkResult = statfs(bundleAbsPath, &stat); - if (checkResult != 0) { + if (checkResult != 0) + { // Meaning the app's bundle is not mounted, that is it's not translocated. // It also means that the app is not loaded. return NO; diff --git a/Installer/Installer-Prefix.pch b/Installer/Installer-Prefix.pch index 6426721c..1f3727bc 100644 --- a/Installer/Installer-Prefix.pch +++ b/Installer/Installer-Prefix.pch @@ -1,20 +1,27 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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. */ // diff --git a/Installer/vChewingInstaller-Bridging-Header.h b/Installer/vChewingInstaller-Bridging-Header.h index 0e096ecb..a0c76f63 100644 --- a/Installer/vChewingInstaller-Bridging-Header.h +++ b/Installer/vChewingInstaller-Bridging-Header.h @@ -1,20 +1,27 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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. */ // diff --git a/Source/3rdParty/OVMandarin/Mandarin.cpp b/Source/3rdParty/OVMandarin/Mandarin.cpp index 53cbd299..4d4cb9a9 100644 --- a/Source/3rdParty/OVMandarin/Mandarin.cpp +++ b/Source/3rdParty/OVMandarin/Mandarin.cpp @@ -1,20 +1,27 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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. */ #include "Mandarin.h" @@ -22,570 +29,744 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR TH #include #include -namespace Mandarin { +namespace Mandarin +{ -class PinyinParseHelper { -public: - static const bool ConsumePrefix(std::string& target, - const std::string& prefix) { - if (target.length() < prefix.length()) { +class PinyinParseHelper +{ + public: + static const bool ConsumePrefix(std::string &target, const std::string &prefix) + { + if (target.length() < prefix.length()) + { return false; } - - if (target.substr(0, prefix.length()) == prefix) { - target = - target.substr(prefix.length(), target.length() - prefix.length()); + + if (target.substr(0, prefix.length()) == prefix) + { + target = target.substr(prefix.length(), target.length() - prefix.length()); return true; } - + return false; } }; -class BopomofoCharacterMap { -public: - static const BopomofoCharacterMap& SharedInstance(); - +class BopomofoCharacterMap +{ + public: + static const BopomofoCharacterMap &SharedInstance(); + std::map componentToCharacter; std::map characterToComponent; - -protected: + + protected: BopomofoCharacterMap(); }; -const BPMF BPMF::FromHanyuPinyin(const std::string& str) { - if (!str.length()) { +const BPMF BPMF::FromHanyuPinyin(const std::string &str) +{ + if (!str.length()) + { return BPMF(); } - + std::string pinyin = str; transform(pinyin.begin(), pinyin.end(), pinyin.begin(), ::tolower); - + BPMF::Component firstComponent = 0; BPMF::Component secondComponent = 0; BPMF::Component thirdComponent = 0; BPMF::Component toneComponent = 0; - + // lookup consonants and consume them bool independentConsonant = false; - + // the y exceptions fist - if (0) { - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "yuan")) { + if (0) + { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "yuan")) + { secondComponent = BPMF::UE; thirdComponent = BPMF::AN; - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "ying")) { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "ying")) + { secondComponent = BPMF::I; thirdComponent = BPMF::ENG; - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "yung")) { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "yung")) + { secondComponent = BPMF::UE; thirdComponent = BPMF::ENG; - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "yong")) { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "yong")) + { secondComponent = BPMF::UE; thirdComponent = BPMF::ENG; - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "yue")) { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "yue")) + { secondComponent = BPMF::UE; thirdComponent = BPMF::E; - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "yun")) { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "yun")) + { secondComponent = BPMF::UE; thirdComponent = BPMF::EN; - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "you")) { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "you")) + { secondComponent = BPMF::I; thirdComponent = BPMF::OU; - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "yu")) { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "yu")) + { secondComponent = BPMF::UE; } - + // try the first character char c = pinyin.length() ? pinyin[0] : 0; - switch (c) { - case 'b': - firstComponent = BPMF::B; - pinyin = pinyin.substr(1); - break; - case 'p': - firstComponent = BPMF::P; - pinyin = pinyin.substr(1); - break; - case 'm': - firstComponent = BPMF::M; - pinyin = pinyin.substr(1); - break; - case 'f': - firstComponent = BPMF::F; - pinyin = pinyin.substr(1); - break; - case 'd': - firstComponent = BPMF::D; - pinyin = pinyin.substr(1); - break; - case 't': - firstComponent = BPMF::T; - pinyin = pinyin.substr(1); - break; - case 'n': - firstComponent = BPMF::N; - pinyin = pinyin.substr(1); - break; - case 'l': - firstComponent = BPMF::L; - pinyin = pinyin.substr(1); - break; - case 'g': - firstComponent = BPMF::G; - pinyin = pinyin.substr(1); - break; - case 'k': - firstComponent = BPMF::K; - pinyin = pinyin.substr(1); - break; - case 'h': - firstComponent = BPMF::H; - pinyin = pinyin.substr(1); - break; - case 'j': - firstComponent = BPMF::J; - pinyin = pinyin.substr(1); - break; - case 'q': - firstComponent = BPMF::Q; - pinyin = pinyin.substr(1); - break; - case 'x': - firstComponent = BPMF::X; - pinyin = pinyin.substr(1); - break; - - // special hanlding for w and y - case 'w': - secondComponent = BPMF::U; - pinyin = pinyin.substr(1); - break; - case 'y': - if (!secondComponent && !thirdComponent) { - secondComponent = BPMF::I; - } - pinyin = pinyin.substr(1); - break; + switch (c) + { + case 'b': + firstComponent = BPMF::B; + pinyin = pinyin.substr(1); + break; + case 'p': + firstComponent = BPMF::P; + pinyin = pinyin.substr(1); + break; + case 'm': + firstComponent = BPMF::M; + pinyin = pinyin.substr(1); + break; + case 'f': + firstComponent = BPMF::F; + pinyin = pinyin.substr(1); + break; + case 'd': + firstComponent = BPMF::D; + pinyin = pinyin.substr(1); + break; + case 't': + firstComponent = BPMF::T; + pinyin = pinyin.substr(1); + break; + case 'n': + firstComponent = BPMF::N; + pinyin = pinyin.substr(1); + break; + case 'l': + firstComponent = BPMF::L; + pinyin = pinyin.substr(1); + break; + case 'g': + firstComponent = BPMF::G; + pinyin = pinyin.substr(1); + break; + case 'k': + firstComponent = BPMF::K; + pinyin = pinyin.substr(1); + break; + case 'h': + firstComponent = BPMF::H; + pinyin = pinyin.substr(1); + break; + case 'j': + firstComponent = BPMF::J; + pinyin = pinyin.substr(1); + break; + case 'q': + firstComponent = BPMF::Q; + pinyin = pinyin.substr(1); + break; + case 'x': + firstComponent = BPMF::X; + pinyin = pinyin.substr(1); + break; + + // special hanlding for w and y + case 'w': + secondComponent = BPMF::U; + pinyin = pinyin.substr(1); + break; + case 'y': + if (!secondComponent && !thirdComponent) + { + secondComponent = BPMF::I; + } + pinyin = pinyin.substr(1); + break; } - + // then we try ZH, CH, SH, R, Z, C, S (in that order) - if (0) { - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "zh")) { + if (0) + { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "zh")) + { firstComponent = BPMF::ZH; independentConsonant = true; - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "ch")) { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "ch")) + { firstComponent = BPMF::CH; independentConsonant = true; - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "sh")) { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "sh")) + { firstComponent = BPMF::SH; independentConsonant = true; - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "r")) { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "r")) + { firstComponent = BPMF::R; independentConsonant = true; - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "z")) { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "z")) + { firstComponent = BPMF::Z; independentConsonant = true; - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "c")) { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "c")) + { firstComponent = BPMF::C; independentConsonant = true; - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "s")) { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "s")) + { firstComponent = BPMF::S; independentConsonant = true; } - + // consume exceptions first: (ien, in), (iou, iu), (uen, un), (veng, iong), // (ven, vn), (uei, ui), ung but longer sequence takes precedence - if (0) { - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "veng")) { + if (0) + { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "veng")) + { secondComponent = BPMF::UE; thirdComponent = BPMF::ENG; - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "iong")) { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "iong")) + { secondComponent = BPMF::UE; thirdComponent = BPMF::ENG; - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "ing")) { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "ing")) + { secondComponent = BPMF::I; thirdComponent = BPMF::ENG; - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "ien")) { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "ien")) + { secondComponent = BPMF::I; thirdComponent = BPMF::EN; - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "iou")) { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "iou")) + { secondComponent = BPMF::I; thirdComponent = BPMF::OU; - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "uen")) { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "uen")) + { secondComponent = BPMF::U; thirdComponent = BPMF::EN; - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "ven")) { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "ven")) + { secondComponent = BPMF::UE; thirdComponent = BPMF::EN; - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "uei")) { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "uei")) + { secondComponent = BPMF::U; thirdComponent = BPMF::EI; - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "ung")) { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "ung")) + { // f exception - if (firstComponent == BPMF::F) { + if (firstComponent == BPMF::F) + { thirdComponent = BPMF::ENG; - } else { + } + else + { secondComponent = BPMF::U; thirdComponent = BPMF::ENG; } - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "ong")) { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "ong")) + { // f exception - if (firstComponent == BPMF::F) { + if (firstComponent == BPMF::F) + { thirdComponent = BPMF::ENG; - } else { + } + else + { secondComponent = BPMF::U; thirdComponent = BPMF::ENG; } - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "un")) { - if (firstComponent == BPMF::J || firstComponent == BPMF::Q || - firstComponent == BPMF::X) { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "un")) + { + if (firstComponent == BPMF::J || firstComponent == BPMF::Q || firstComponent == BPMF::X) + { secondComponent = BPMF::UE; - } else { + } + else + { secondComponent = BPMF::U; } thirdComponent = BPMF::EN; - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "iu")) { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "iu")) + { secondComponent = BPMF::I; thirdComponent = BPMF::OU; - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "in")) { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "in")) + { secondComponent = BPMF::I; thirdComponent = BPMF::EN; - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "vn")) { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "vn")) + { secondComponent = BPMF::UE; thirdComponent = BPMF::EN; - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "ui")) { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "ui")) + { secondComponent = BPMF::U; thirdComponent = BPMF::EI; - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "ue")) { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "ue")) + { secondComponent = BPMF::UE; thirdComponent = BPMF::E; - } else if (PinyinParseHelper::ConsumePrefix(pinyin, u8"ü")) { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, u8"ü")) + { secondComponent = BPMF::UE; } - + // then consume the middle component... - if (0) { - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "i")) { + if (0) + { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "i")) + { secondComponent = independentConsonant ? 0 : BPMF::I; - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "u")) { - if (firstComponent == BPMF::J || firstComponent == BPMF::Q || - firstComponent == BPMF::X) { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "u")) + { + if (firstComponent == BPMF::J || firstComponent == BPMF::Q || firstComponent == BPMF::X) + { secondComponent = BPMF::UE; - } else { + } + else + { secondComponent = BPMF::U; } - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "v")) { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "v")) + { secondComponent = BPMF::UE; } - + // the vowels, longer sequence takes precedence - if (0) { - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "ang")) { + if (0) + { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "ang")) + { thirdComponent = BPMF::ANG; - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "eng")) { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "eng")) + { thirdComponent = BPMF::ENG; - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "err")) { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "err")) + { thirdComponent = BPMF::ERR; - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "ai")) { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "ai")) + { thirdComponent = BPMF::AI; - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "ei")) { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "ei")) + { thirdComponent = BPMF::EI; - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "ao")) { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "ao")) + { thirdComponent = BPMF::AO; - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "ou")) { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "ou")) + { thirdComponent = BPMF::OU; - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "an")) { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "an")) + { thirdComponent = BPMF::AN; - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "en")) { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "en")) + { thirdComponent = BPMF::EN; - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "er")) { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "er")) + { thirdComponent = BPMF::ERR; - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "a")) { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "a")) + { thirdComponent = BPMF::A; - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "o")) { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "o")) + { thirdComponent = BPMF::O; - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "e")) { - if (secondComponent) { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "e")) + { + if (secondComponent) + { thirdComponent = BPMF::E; - } else { + } + else + { thirdComponent = BPMF::ER; } } - + // at last! - if (0) { - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "1")) { + if (0) + { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "1")) + { toneComponent = BPMF::Tone1; - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "2")) { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "2")) + { toneComponent = BPMF::Tone2; - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "3")) { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "3")) + { toneComponent = BPMF::Tone3; - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "4")) { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "4")) + { toneComponent = BPMF::Tone4; - } else if (PinyinParseHelper::ConsumePrefix(pinyin, "5")) { + } + else if (PinyinParseHelper::ConsumePrefix(pinyin, "5")) + { toneComponent = BPMF::Tone5; } - - return BPMF(firstComponent | secondComponent | thirdComponent | - toneComponent); + + return BPMF(firstComponent | secondComponent | thirdComponent | toneComponent); } -const std::string BPMF::HanyuPinyinString(bool includesTone, - bool useVForUUmlaut) const { +const std::string BPMF::HanyuPinyinString(bool includesTone, bool useVForUUmlaut) const +{ std::string consonant, middle, vowel, tone; - - Component cc = consonantComponent(), mvc = middleVowelComponent(), - vc = vowelComponent(); + + Component cc = consonantComponent(), mvc = middleVowelComponent(), vc = vowelComponent(); bool hasNoMVCOrVC = !(mvc || vc); - - switch (cc) { - case B: - consonant = "b"; - break; - case P: - consonant = "p"; - break; - case M: - consonant = "m"; - break; - case F: - consonant = "f"; - break; - case D: - consonant = "d"; - break; - case T: - consonant = "t"; - break; - case N: - consonant = "n"; - break; - case L: - consonant = "l"; - break; - case G: - consonant = "g"; - break; - case K: - consonant = "k"; - break; - case H: - consonant = "h"; - break; - case J: - consonant = "j"; - if (hasNoMVCOrVC) middle = "i"; - break; - case Q: - consonant = "q"; - if (hasNoMVCOrVC) middle = "i"; - break; - case X: - consonant = "x"; - if (hasNoMVCOrVC) middle = "i"; - break; - case ZH: - consonant = "zh"; - if (hasNoMVCOrVC) middle = "i"; - break; - case CH: - consonant = "ch"; - if (hasNoMVCOrVC) middle = "i"; - break; - case SH: - consonant = "sh"; - if (hasNoMVCOrVC) middle = "i"; - break; - case R: - consonant = "r"; - if (hasNoMVCOrVC) middle = "i"; - break; - case Z: - consonant = "z"; - if (hasNoMVCOrVC) middle = "i"; - break; - case C: - consonant = "c"; - if (hasNoMVCOrVC) middle = "i"; - break; - case S: - consonant = "s"; - if (hasNoMVCOrVC) middle = "i"; - break; + + switch (cc) + { + case B: + consonant = "b"; + break; + case P: + consonant = "p"; + break; + case M: + consonant = "m"; + break; + case F: + consonant = "f"; + break; + case D: + consonant = "d"; + break; + case T: + consonant = "t"; + break; + case N: + consonant = "n"; + break; + case L: + consonant = "l"; + break; + case G: + consonant = "g"; + break; + case K: + consonant = "k"; + break; + case H: + consonant = "h"; + break; + case J: + consonant = "j"; + if (hasNoMVCOrVC) + middle = "i"; + break; + case Q: + consonant = "q"; + if (hasNoMVCOrVC) + middle = "i"; + break; + case X: + consonant = "x"; + if (hasNoMVCOrVC) + middle = "i"; + break; + case ZH: + consonant = "zh"; + if (hasNoMVCOrVC) + middle = "i"; + break; + case CH: + consonant = "ch"; + if (hasNoMVCOrVC) + middle = "i"; + break; + case SH: + consonant = "sh"; + if (hasNoMVCOrVC) + middle = "i"; + break; + case R: + consonant = "r"; + if (hasNoMVCOrVC) + middle = "i"; + break; + case Z: + consonant = "z"; + if (hasNoMVCOrVC) + middle = "i"; + break; + case C: + consonant = "c"; + if (hasNoMVCOrVC) + middle = "i"; + break; + case S: + consonant = "s"; + if (hasNoMVCOrVC) + middle = "i"; + break; } - - switch (mvc) { - case I: - if (!cc) { - consonant = "y"; - } - - middle = (!vc || cc) ? "i" : ""; - break; - case U: - if (!cc) { - consonant = "w"; - } - middle = (!vc || cc) ? "u" : ""; - break; - case UE: - if (!cc) { - consonant = "y"; - } - - if ((cc == N || cc == L) && vc != E) { - middle = useVForUUmlaut ? "v" : "ü"; - } else { - middle = "u"; - } - - break; + + switch (mvc) + { + case I: + if (!cc) + { + consonant = "y"; + } + + middle = (!vc || cc) ? "i" : ""; + break; + case U: + if (!cc) + { + consonant = "w"; + } + middle = (!vc || cc) ? "u" : ""; + break; + case UE: + if (!cc) + { + consonant = "y"; + } + + if ((cc == N || cc == L) && vc != E) + { + middle = useVForUUmlaut ? "v" : "ü"; + } + else + { + middle = "u"; + } + + break; } - - switch (vc) { - case A: - vowel = "a"; - break; - case O: - vowel = "o"; - break; - case ER: - vowel = "e"; - break; - case E: - vowel = "e"; - break; - case AI: - vowel = "ai"; - break; - case EI: - vowel = "ei"; - break; - case AO: - vowel = "ao"; - break; - case OU: - vowel = "ou"; - break; - case AN: - vowel = "an"; - break; - case EN: - vowel = "en"; - break; - case ANG: - vowel = "ang"; - break; - case ENG: - vowel = "eng"; - break; - case ERR: - vowel = "er"; - break; + + switch (vc) + { + case A: + vowel = "a"; + break; + case O: + vowel = "o"; + break; + case ER: + vowel = "e"; + break; + case E: + vowel = "e"; + break; + case AI: + vowel = "ai"; + break; + case EI: + vowel = "ei"; + break; + case AO: + vowel = "ao"; + break; + case OU: + vowel = "ou"; + break; + case AN: + vowel = "an"; + break; + case EN: + vowel = "en"; + break; + case ANG: + vowel = "ang"; + break; + case ENG: + vowel = "eng"; + break; + case ERR: + vowel = "er"; + break; } - + // combination rules - + // ueng -> ong, but note "weng" - if ((mvc == U || mvc == UE) && vc == ENG) { + if ((mvc == U || mvc == UE) && vc == ENG) + { middle = ""; - vowel = (cc == J || cc == Q || cc == X) - ? "iong" - : ((!cc && mvc == U) ? "eng" : "ong"); + vowel = (cc == J || cc == Q || cc == X) ? "iong" : ((!cc && mvc == U) ? "eng" : "ong"); } - + // ien, uen, üen -> in, un, ün ; but note "wen", "yin" and "yun" - if (mvc && vc == EN) { - if (cc) { + if (mvc && vc == EN) + { + if (cc) + { vowel = "n"; - } else { - if (mvc == UE) { - vowel = "n"; // yun - } else if (mvc == U) { - vowel = "en"; // wen - } else { - vowel = "in"; // yin + } + else + { + if (mvc == UE) + { + vowel = "n"; // yun + } + else if (mvc == U) + { + vowel = "en"; // wen + } + else + { + vowel = "in"; // yin } } } - + // iou -> iu - if (cc && mvc == I && vc == OU) { + if (cc && mvc == I && vc == OU) + { middle = ""; vowel = "iu"; } - + // ieng -> ing - if (mvc == I && vc == ENG) { + if (mvc == I && vc == ENG) + { middle = ""; vowel = "ing"; } - + // uei -> ui - if (cc && mvc == U && vc == EI) { + if (cc && mvc == U && vc == EI) + { middle = ""; vowel = "ui"; } - - if (includesTone) { - switch (toneMarkerComponent()) { - case Tone2: - tone = "2"; - break; - case Tone3: - tone = "3"; - break; - case Tone4: - tone = "4"; - break; - case Tone5: - tone = "5"; - break; + + if (includesTone) + { + switch (toneMarkerComponent()) + { + case Tone2: + tone = "2"; + break; + case Tone3: + tone = "3"; + break; + case Tone4: + tone = "4"; + break; + case Tone5: + tone = "5"; + break; } } - + return consonant + middle + vowel + tone; } -const BPMF BPMF::FromComposedString(const std::string& str) { +const BPMF BPMF::FromComposedString(const std::string &str) +{ BPMF syllable; auto iter = str.begin(); - while (iter != str.end()) { + while (iter != str.end()) + { // This is a naive implementation and we bail early at anything we don't // recognize. A sound implementation would require to either use a trie for // the Bopomofo character map or to split the input by codepoints. This // suffices for now. - + // Illegal. - if (!(*iter & 0x80)) { + if (!(*iter & 0x80)) + { break; } - + size_t utf8_length = -1; - + // These are the code points for the tone markers. - if ((*iter & (0x80 | 0x40)) && !(*iter & 0x20)) { + if ((*iter & (0x80 | 0x40)) && !(*iter & 0x20)) + { utf8_length = 2; - } else if ((*iter & (0x80 | 0x40 | 0x20)) && !(*iter & 0x10)) { + } + else if ((*iter & (0x80 | 0x40 | 0x20)) && !(*iter & 0x10)) + { utf8_length = 3; - } else { + } + else + { // Illegal. break; } - - if (iter + (utf8_length - 1) == str.end()) { + + if (iter + (utf8_length - 1) == str.end()) + { break; } - + std::string component = std::string(iter, iter + utf8_length); - const std::map& charToComp = - BopomofoCharacterMap::SharedInstance().characterToComponent; - std::map::const_iterator result = - charToComp.find(component); - if (result == charToComp.end()) { + const std::map &charToComp = + BopomofoCharacterMap::SharedInstance().characterToComponent; + std::map::const_iterator result = charToComp.find(component); + if (result == charToComp.end()) + { break; - } else { + } + else + { syllable += BPMF((*result).second); } iter += utf8_length; @@ -593,14 +774,12 @@ const BPMF BPMF::FromComposedString(const std::string& str) { return syllable; } -const std::string BPMF::composedString() const { +const std::string BPMF::composedString() const +{ std::string result; -#define APPEND(c) \ -if (syllable_ & c) \ -result += \ -(*BopomofoCharacterMap::SharedInstance().componentToCharacter.find( \ -syllable_ & c)) \ -.second +#define APPEND(c) \ + if (syllable_ & c) \ + result += (*BopomofoCharacterMap::SharedInstance().componentToCharacter.find(syllable_ & c)).second APPEND(ConsonantMask); APPEND(MiddleVowelMask); APPEND(VowelMask); @@ -609,14 +788,14 @@ syllable_ & c)) \ return result; } - - -const BopomofoCharacterMap& BopomofoCharacterMap::SharedInstance() { - static BopomofoCharacterMap* map = new BopomofoCharacterMap(); +const BopomofoCharacterMap &BopomofoCharacterMap::SharedInstance() +{ + static BopomofoCharacterMap *map = new BopomofoCharacterMap(); return *map; } -BopomofoCharacterMap::BopomofoCharacterMap() { +BopomofoCharacterMap::BopomofoCharacterMap() +{ characterToComponent[u8"ㄅ"] = BPMF::B; characterToComponent[u8"ㄆ"] = BPMF::P; characterToComponent[u8"ㄇ"] = BPMF::M; @@ -658,27 +837,24 @@ BopomofoCharacterMap::BopomofoCharacterMap() { characterToComponent[u8"ˇ"] = BPMF::Tone3; characterToComponent[u8"ˋ"] = BPMF::Tone4; characterToComponent[u8"˙"] = BPMF::Tone5; - - for (std::map::iterator iter = - characterToComponent.begin(); + + for (std::map::iterator iter = characterToComponent.begin(); iter != characterToComponent.end(); ++iter) componentToCharacter[(*iter).second] = (*iter).first; } -#define ASSIGNKEY1(m, vec, k, val) \ -m[k] = (vec.clear(), vec.push_back((BPMF::Component)val), vec) -#define ASSIGNKEY2(m, vec, k, val1, val2) \ -m[k] = (vec.clear(), vec.push_back((BPMF::Component)val1), \ -vec.push_back((BPMF::Component)val2), vec) -#define ASSIGNKEY3(m, vec, k, val1, val2, val3) \ -m[k] = (vec.clear(), vec.push_back((BPMF::Component)val1), \ -vec.push_back((BPMF::Component)val2), \ -vec.push_back((BPMF::Component)val3), vec) +#define ASSIGNKEY1(m, vec, k, val) m[k] = (vec.clear(), vec.push_back((BPMF::Component)val), vec) +#define ASSIGNKEY2(m, vec, k, val1, val2) \ + m[k] = (vec.clear(), vec.push_back((BPMF::Component)val1), vec.push_back((BPMF::Component)val2), vec) +#define ASSIGNKEY3(m, vec, k, val1, val2, val3) \ + m[k] = (vec.clear(), vec.push_back((BPMF::Component)val1), vec.push_back((BPMF::Component)val2), \ + vec.push_back((BPMF::Component)val3), vec) -static BopomofoKeyboardLayout* CreateStandardLayout() { +static BopomofoKeyboardLayout *CreateStandardLayout() +{ std::vector vec; BopomofoKeyToComponentMap ktcm; - + ASSIGNKEY1(ktcm, vec, '1', BPMF::B); ASSIGNKEY1(ktcm, vec, 'q', BPMF::P); ASSIGNKEY1(ktcm, vec, 'a', BPMF::M); @@ -720,14 +896,15 @@ static BopomofoKeyboardLayout* CreateStandardLayout() { ASSIGNKEY1(ktcm, vec, '4', BPMF::Tone4); ASSIGNKEY1(ktcm, vec, '6', BPMF::Tone2); ASSIGNKEY1(ktcm, vec, '7', BPMF::Tone5); - + return new BopomofoKeyboardLayout(ktcm, "Standard"); } -static BopomofoKeyboardLayout* CreateIBMLayout() { +static BopomofoKeyboardLayout *CreateIBMLayout() +{ std::vector vec; BopomofoKeyToComponentMap ktcm; - + ASSIGNKEY1(ktcm, vec, '1', BPMF::B); ASSIGNKEY1(ktcm, vec, '2', BPMF::P); ASSIGNKEY1(ktcm, vec, '3', BPMF::M); @@ -769,14 +946,15 @@ static BopomofoKeyboardLayout* CreateIBMLayout() { ASSIGNKEY1(ktcm, vec, ',', BPMF::Tone3); ASSIGNKEY1(ktcm, vec, '.', BPMF::Tone4); ASSIGNKEY1(ktcm, vec, '/', BPMF::Tone5); - + return new BopomofoKeyboardLayout(ktcm, "IBM"); } -static BopomofoKeyboardLayout* CreateMiTACLayout() { +static BopomofoKeyboardLayout *CreateMiTACLayout() +{ std::vector vec; BopomofoKeyToComponentMap ktcm; - + ASSIGNKEY1(ktcm, vec, '1', BPMF::Tone5); ASSIGNKEY1(ktcm, vec, '2', BPMF::Tone2); ASSIGNKEY1(ktcm, vec, '3', BPMF::Tone3); @@ -818,14 +996,15 @@ static BopomofoKeyboardLayout* CreateMiTACLayout() { ASSIGNKEY1(ktcm, vec, 'x', BPMF::X); ASSIGNKEY1(ktcm, vec, 'y', BPMF::I); ASSIGNKEY1(ktcm, vec, 'z', BPMF::Z); - + return new BopomofoKeyboardLayout(ktcm, "MiTAC"); } -static BopomofoKeyboardLayout* CreateETenLayout() { +static BopomofoKeyboardLayout *CreateETenLayout() +{ std::vector vec; BopomofoKeyToComponentMap ktcm; - + ASSIGNKEY1(ktcm, vec, 'b', BPMF::B); ASSIGNKEY1(ktcm, vec, 'p', BPMF::P); ASSIGNKEY1(ktcm, vec, 'm', BPMF::M); @@ -867,14 +1046,15 @@ static BopomofoKeyboardLayout* CreateETenLayout() { ASSIGNKEY1(ktcm, vec, '3', BPMF::Tone3); ASSIGNKEY1(ktcm, vec, '4', BPMF::Tone4); ASSIGNKEY1(ktcm, vec, '1', BPMF::Tone5); - + return new BopomofoKeyboardLayout(ktcm, "ETen"); } -static BopomofoKeyboardLayout* CreateHsuLayout() { +static BopomofoKeyboardLayout *CreateHsuLayout() +{ std::vector vec; BopomofoKeyToComponentMap ktcm; - + ASSIGNKEY1(ktcm, vec, 'b', BPMF::B); ASSIGNKEY1(ktcm, vec, 'p', BPMF::P); ASSIGNKEY2(ktcm, vec, 'm', BPMF::M, BPMF::AN); @@ -900,14 +1080,15 @@ static BopomofoKeyboardLayout* CreateHsuLayout() { ASSIGNKEY1(ktcm, vec, 'i', BPMF::AI); ASSIGNKEY1(ktcm, vec, 'w', BPMF::AO); ASSIGNKEY1(ktcm, vec, 'o', BPMF::OU); - + return new BopomofoKeyboardLayout(ktcm, "Hsu"); } -static BopomofoKeyboardLayout* CreateETen26Layout() { +static BopomofoKeyboardLayout *CreateETen26Layout() +{ std::vector vec; BopomofoKeyToComponentMap ktcm; - + ASSIGNKEY1(ktcm, vec, 'b', BPMF::B); ASSIGNKEY2(ktcm, vec, 'p', BPMF::P, BPMF::OU); ASSIGNKEY2(ktcm, vec, 'm', BPMF::M, BPMF::AN); @@ -934,14 +1115,15 @@ static BopomofoKeyboardLayout* CreateETen26Layout() { ASSIGNKEY1(ktcm, vec, 'r', BPMF::ER); ASSIGNKEY1(ktcm, vec, 'i', BPMF::AI); ASSIGNKEY1(ktcm, vec, 'z', BPMF::AO); - + return new BopomofoKeyboardLayout(ktcm, "ETen26"); } -static BopomofoKeyboardLayout* CreateFakeSeigyouLayout() { +static BopomofoKeyboardLayout *CreateFakeSeigyouLayout() +{ std::vector vec; BopomofoKeyToComponentMap ktcm; - + ASSIGNKEY1(ktcm, vec, '1', BPMF::Tone5); ASSIGNKEY1(ktcm, vec, '2', BPMF::B); ASSIGNKEY1(ktcm, vec, '3', BPMF::D); @@ -983,55 +1165,62 @@ static BopomofoKeyboardLayout* CreateFakeSeigyouLayout() { ASSIGNKEY1(ktcm, vec, 'x', BPMF::F); ASSIGNKEY1(ktcm, vec, 'y', BPMF::CH); ASSIGNKEY1(ktcm, vec, 'z', BPMF::Tone4); - + return new BopomofoKeyboardLayout(ktcm, "FakeSeigyou"); } -static BopomofoKeyboardLayout* CreateHanyuPinyinLayout() { +static BopomofoKeyboardLayout *CreateHanyuPinyinLayout() +{ BopomofoKeyToComponentMap ktcm; return new BopomofoKeyboardLayout(ktcm, "HanyuPinyin"); } -const BopomofoKeyboardLayout* BopomofoKeyboardLayout::StandardLayout() { - static BopomofoKeyboardLayout* layout = CreateStandardLayout(); +const BopomofoKeyboardLayout *BopomofoKeyboardLayout::StandardLayout() +{ + static BopomofoKeyboardLayout *layout = CreateStandardLayout(); return layout; } -const BopomofoKeyboardLayout* BopomofoKeyboardLayout::ETenLayout() { - static BopomofoKeyboardLayout* layout = CreateETenLayout(); +const BopomofoKeyboardLayout *BopomofoKeyboardLayout::ETenLayout() +{ + static BopomofoKeyboardLayout *layout = CreateETenLayout(); return layout; } -const BopomofoKeyboardLayout* BopomofoKeyboardLayout::HsuLayout() { - static BopomofoKeyboardLayout* layout = CreateHsuLayout(); +const BopomofoKeyboardLayout *BopomofoKeyboardLayout::HsuLayout() +{ + static BopomofoKeyboardLayout *layout = CreateHsuLayout(); return layout; } -const BopomofoKeyboardLayout* BopomofoKeyboardLayout::ETen26Layout() { - static BopomofoKeyboardLayout* layout = CreateETen26Layout(); +const BopomofoKeyboardLayout *BopomofoKeyboardLayout::ETen26Layout() +{ + static BopomofoKeyboardLayout *layout = CreateETen26Layout(); return layout; } -const BopomofoKeyboardLayout* BopomofoKeyboardLayout::IBMLayout() { - static BopomofoKeyboardLayout* layout = CreateIBMLayout(); +const BopomofoKeyboardLayout *BopomofoKeyboardLayout::IBMLayout() +{ + static BopomofoKeyboardLayout *layout = CreateIBMLayout(); return layout; } -const BopomofoKeyboardLayout* BopomofoKeyboardLayout::MiTACLayout() { - static BopomofoKeyboardLayout* layout = CreateMiTACLayout(); +const BopomofoKeyboardLayout *BopomofoKeyboardLayout::MiTACLayout() +{ + static BopomofoKeyboardLayout *layout = CreateMiTACLayout(); return layout; } -const BopomofoKeyboardLayout* BopomofoKeyboardLayout::FakeSeigyouLayout() { - static BopomofoKeyboardLayout* layout = CreateFakeSeigyouLayout(); +const BopomofoKeyboardLayout *BopomofoKeyboardLayout::FakeSeigyouLayout() +{ + static BopomofoKeyboardLayout *layout = CreateFakeSeigyouLayout(); return layout; } -const BopomofoKeyboardLayout* BopomofoKeyboardLayout::HanyuPinyinLayout() { - static BopomofoKeyboardLayout* layout = CreateHanyuPinyinLayout(); +const BopomofoKeyboardLayout *BopomofoKeyboardLayout::HanyuPinyinLayout() +{ + static BopomofoKeyboardLayout *layout = CreateHanyuPinyinLayout(); return layout; } -} // namespace Mandarin - - +} // namespace Mandarin diff --git a/Source/3rdParty/OVMandarin/Mandarin.h b/Source/3rdParty/OVMandarin/Mandarin.h index e8a24708..61697c1c 100644 --- a/Source/3rdParty/OVMandarin/Mandarin.h +++ b/Source/3rdParty/OVMandarin/Mandarin.h @@ -1,20 +1,27 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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. */ #ifndef MANDARIN_H_ @@ -25,79 +32,115 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR TH #include #include -namespace Mandarin { +namespace Mandarin +{ -class BopomofoSyllable { -public: +class BopomofoSyllable +{ + public: typedef uint16_t Component; - - explicit BopomofoSyllable(Component syllable = 0) : syllable_(syllable) {} - - BopomofoSyllable(const BopomofoSyllable&) = default; - BopomofoSyllable(BopomofoSyllable&& another) = default; - BopomofoSyllable& operator=(const BopomofoSyllable&) = default; - BopomofoSyllable& operator=(BopomofoSyllable&&) = default; - + + explicit BopomofoSyllable(Component syllable = 0) : syllable_(syllable) + { + } + + BopomofoSyllable(const BopomofoSyllable &) = default; + BopomofoSyllable(BopomofoSyllable &&another) = default; + BopomofoSyllable &operator=(const BopomofoSyllable &) = default; + BopomofoSyllable &operator=(BopomofoSyllable &&) = default; + // takes the ASCII-form, "v"-tolerant, TW-style Hanyu Pinyin (fong, pong, bong // acceptable) - static const BopomofoSyllable FromHanyuPinyin(const std::string& str); - + static const BopomofoSyllable FromHanyuPinyin(const std::string &str); + // TO DO: Support accented vowels - const std::string HanyuPinyinString(bool includesTone, - bool useVForUUmlaut) const; - - static const BopomofoSyllable FromComposedString(const std::string& str); + const std::string HanyuPinyinString(bool includesTone, bool useVForUUmlaut) const; + + static const BopomofoSyllable FromComposedString(const std::string &str); const std::string composedString() const; - - void clear() { syllable_ = 0; } - - bool isEmpty() const { return !syllable_; } - - bool hasConsonant() const { return !!(syllable_ & ConsonantMask); } - - bool hasMiddleVowel() const { return !!(syllable_ & MiddleVowelMask); } - bool hasVowel() const { return !!(syllable_ & VowelMask); } - - bool hasToneMarker() const { return !!(syllable_ & ToneMarkerMask); } - - Component consonantComponent() const { return syllable_ & ConsonantMask; } - - Component middleVowelComponent() const { + + void clear() + { + syllable_ = 0; + } + + bool isEmpty() const + { + return !syllable_; + } + + bool hasConsonant() const + { + return !!(syllable_ & ConsonantMask); + } + + bool hasMiddleVowel() const + { + return !!(syllable_ & MiddleVowelMask); + } + bool hasVowel() const + { + return !!(syllable_ & VowelMask); + } + + bool hasToneMarker() const + { + return !!(syllable_ & ToneMarkerMask); + } + + Component consonantComponent() const + { + return syllable_ & ConsonantMask; + } + + Component middleVowelComponent() const + { return syllable_ & MiddleVowelMask; } - - Component vowelComponent() const { return syllable_ & VowelMask; } - - Component toneMarkerComponent() const { return syllable_ & ToneMarkerMask; } - - bool operator==(const BopomofoSyllable& another) const { + + Component vowelComponent() const + { + return syllable_ & VowelMask; + } + + Component toneMarkerComponent() const + { + return syllable_ & ToneMarkerMask; + } + + bool operator==(const BopomofoSyllable &another) const + { return syllable_ == another.syllable_; } - - bool operator!=(const BopomofoSyllable& another) const { + + bool operator!=(const BopomofoSyllable &another) const + { return syllable_ != another.syllable_; } - - bool isOverlappingWith(const BopomofoSyllable& another) const { + + bool isOverlappingWith(const BopomofoSyllable &another) const + { #define IOW_SAND(mask) ((syllable_ & mask) && (another.syllable_ & mask)) - return IOW_SAND(ConsonantMask) || IOW_SAND(MiddleVowelMask) || - IOW_SAND(VowelMask) || IOW_SAND(ToneMarkerMask); + return IOW_SAND(ConsonantMask) || IOW_SAND(MiddleVowelMask) || IOW_SAND(VowelMask) || IOW_SAND(ToneMarkerMask); #undef IOW_SAND } - + // consonants J, Q, X all require the existence of vowel I or UE - bool belongsToJQXClass() const { + bool belongsToJQXClass() const + { Component consonant = syllable_ & ConsonantMask; return (consonant == J || consonant == Q || consonant == X); } - + // zi, ci, si, chi, chi, shi, ri - bool belongsToZCSRClass() const { + bool belongsToZCSRClass() const + { Component consonant = syllable_ & ConsonantMask; return (consonant >= ZH && consonant <= S); } - - Component maskType() const { + + Component maskType() const + { Component mask = 0; mask |= (syllable_ & ConsonantMask) ? ConsonantMask : 0; mask |= (syllable_ & MiddleVowelMask) ? MiddleVowelMask : 0; @@ -105,13 +148,15 @@ public: mask |= (syllable_ & ToneMarkerMask) ? ToneMarkerMask : 0; return mask; } - - const BopomofoSyllable operator+(const BopomofoSyllable& another) const { + + const BopomofoSyllable operator+(const BopomofoSyllable &another) const + { Component newSyllable = syllable_; -#define OP_SOVER(mask) \ -if (another.syllable_ & mask) { \ -newSyllable = (newSyllable & ~mask) | (another.syllable_ & mask); \ -} +#define OP_SOVER(mask) \ + if (another.syllable_ & mask) \ + { \ + newSyllable = (newSyllable & ~mask) | (another.syllable_ & mask); \ + } OP_SOVER(ConsonantMask); OP_SOVER(MiddleVowelMask); OP_SOVER(VowelMask); @@ -119,12 +164,14 @@ newSyllable = (newSyllable & ~mask) | (another.syllable_ & mask); \ #undef OP_SOVER return BopomofoSyllable(newSyllable); } - - BopomofoSyllable& operator+=(const BopomofoSyllable& another) { -#define OPE_SOVER(mask) \ -if (another.syllable_ & mask) { \ -syllable_ = (syllable_ & ~mask) | (another.syllable_ & mask); \ -} + + BopomofoSyllable &operator+=(const BopomofoSyllable &another) + { +#define OPE_SOVER(mask) \ + if (another.syllable_ & mask) \ + { \ + syllable_ = (syllable_ & ~mask) | (another.syllable_ & mask); \ + } OPE_SOVER(ConsonantMask); OPE_SOVER(MiddleVowelMask); OPE_SOVER(VowelMask); @@ -132,87 +179,88 @@ syllable_ = (syllable_ & ~mask) | (another.syllable_ & mask); \ #undef OPE_SOVER return *this; } - - friend std::ostream& operator<<(std::ostream& stream, - const BopomofoSyllable& syllable); - - static constexpr Component - ConsonantMask = 0x001f, // 0000 0000 0001 1111, 21 consonants - MiddleVowelMask = 0x0060, // 0000 0000 0110 0000, 3 middle vowels - VowelMask = 0x0780, // 0000 0111 1000 0000, 13 vowels - ToneMarkerMask = 0x3800, // 0011 1000 0000 0000, 5 tones (tone1 = 0x00) - B = 0x0001, P = 0x0002, M = 0x0003, F = 0x0004, D = 0x0005, T = 0x0006, - N = 0x0007, L = 0x0008, G = 0x0009, K = 0x000a, H = 0x000b, J = 0x000c, - Q = 0x000d, X = 0x000e, ZH = 0x000f, CH = 0x0010, SH = 0x0011, R = 0x0012, - Z = 0x0013, C = 0x0014, S = 0x0015, I = 0x0020, U = 0x0040, - UE = 0x0060, // ue = u umlaut (we use the German convention here as an - // ersatz to the /ju:/ sound) - A = 0x0080, O = 0x0100, ER = 0x0180, E = 0x0200, AI = 0x0280, EI = 0x0300, - AO = 0x0380, OU = 0x0400, AN = 0x0480, EN = 0x0500, ANG = 0x0580, - ENG = 0x0600, ERR = 0x0680, Tone1 = 0x0000, Tone2 = 0x0800, - Tone3 = 0x1000, Tone4 = 0x1800, Tone5 = 0x2000; - -protected: + + friend std::ostream &operator<<(std::ostream &stream, const BopomofoSyllable &syllable); + + static constexpr Component ConsonantMask = 0x001f, // 0000 0000 0001 1111, 21 consonants + MiddleVowelMask = 0x0060, // 0000 0000 0110 0000, 3 middle vowels + VowelMask = 0x0780, // 0000 0111 1000 0000, 13 vowels + ToneMarkerMask = 0x3800, // 0011 1000 0000 0000, 5 tones (tone1 = 0x00) + B = 0x0001, P = 0x0002, M = 0x0003, F = 0x0004, D = 0x0005, T = 0x0006, N = 0x0007, L = 0x0008, G = 0x0009, + K = 0x000a, H = 0x000b, J = 0x000c, Q = 0x000d, X = 0x000e, ZH = 0x000f, CH = 0x0010, + SH = 0x0011, R = 0x0012, Z = 0x0013, C = 0x0014, S = 0x0015, I = 0x0020, U = 0x0040, + UE = 0x0060, // ue = u umlaut (we use the German convention here as an + // ersatz to the /ju:/ sound) + A = 0x0080, O = 0x0100, ER = 0x0180, E = 0x0200, AI = 0x0280, EI = 0x0300, AO = 0x0380, OU = 0x0400, + AN = 0x0480, EN = 0x0500, ANG = 0x0580, ENG = 0x0600, ERR = 0x0680, Tone1 = 0x0000, + Tone2 = 0x0800, Tone3 = 0x1000, Tone4 = 0x1800, Tone5 = 0x2000; + + protected: Component syllable_; }; -inline std::ostream& operator<<(std::ostream& stream, - const BopomofoSyllable& syllable) { +inline std::ostream &operator<<(std::ostream &stream, const BopomofoSyllable &syllable) +{ stream << syllable.composedString(); return stream; } typedef BopomofoSyllable BPMF; -typedef std::map > BopomofoKeyToComponentMap; +typedef std::map> BopomofoKeyToComponentMap; typedef std::map BopomofoComponentToKeyMap; -class BopomofoKeyboardLayout { -public: - static const BopomofoKeyboardLayout* StandardLayout(); - static const BopomofoKeyboardLayout* ETenLayout(); - static const BopomofoKeyboardLayout* HsuLayout(); - static const BopomofoKeyboardLayout* ETen26Layout(); - static const BopomofoKeyboardLayout* IBMLayout(); - static const BopomofoKeyboardLayout* MiTACLayout(); - static const BopomofoKeyboardLayout* FakeSeigyouLayout(); - static const BopomofoKeyboardLayout* HanyuPinyinLayout(); - - BopomofoKeyboardLayout(const BopomofoKeyToComponentMap& ktcm, - const std::string& name) - : m_keyToComponent(ktcm), m_name(name) { - for (BopomofoKeyToComponentMap::const_iterator miter = - m_keyToComponent.begin(); +class BopomofoKeyboardLayout +{ + public: + static const BopomofoKeyboardLayout *StandardLayout(); + static const BopomofoKeyboardLayout *ETenLayout(); + static const BopomofoKeyboardLayout *HsuLayout(); + static const BopomofoKeyboardLayout *ETen26Layout(); + static const BopomofoKeyboardLayout *IBMLayout(); + static const BopomofoKeyboardLayout *MiTACLayout(); + static const BopomofoKeyboardLayout *FakeSeigyouLayout(); + static const BopomofoKeyboardLayout *HanyuPinyinLayout(); + + BopomofoKeyboardLayout(const BopomofoKeyToComponentMap &ktcm, const std::string &name) + : m_keyToComponent(ktcm), m_name(name) + { + for (BopomofoKeyToComponentMap::const_iterator miter = m_keyToComponent.begin(); miter != m_keyToComponent.end(); ++miter) - for (std::vector::const_iterator viter = - (*miter).second.begin(); + for (std::vector::const_iterator viter = (*miter).second.begin(); viter != (*miter).second.end(); ++viter) m_componentToKey[*viter] = (*miter).first; } - - const std::string name() const { return m_name; } - - char componentToKey(BPMF::Component component) const { - BopomofoComponentToKeyMap::const_iterator iter = - m_componentToKey.find(component); + + const std::string name() const + { + return m_name; + } + + char componentToKey(BPMF::Component component) const + { + BopomofoComponentToKeyMap::const_iterator iter = m_componentToKey.find(component); return (iter == m_componentToKey.end()) ? 0 : (*iter).second; } - - const std::vector keyToComponents(char key) const { + + const std::vector keyToComponents(char key) const + { BopomofoKeyToComponentMap::const_iterator iter = m_keyToComponent.find(key); - return (iter == m_keyToComponent.end()) ? std::vector() - : (*iter).second; + return (iter == m_keyToComponent.end()) ? std::vector() : (*iter).second; } - - const std::string keySequenceFromSyllable(BPMF syllable) const { + + const std::string keySequenceFromSyllable(BPMF syllable) const + { std::string sequence; - + BPMF::Component c; char k; -#define STKS_COMBINE(component) \ -if ((c = component)) { \ -if ((k = componentToKey(c))) sequence += std::string(1, k); \ -} +#define STKS_COMBINE(component) \ + if ((c = component)) \ + { \ + if ((k = componentToKey(c))) \ + sequence += std::string(1, k); \ + } STKS_COMBINE(syllable.consonantComponent()); STKS_COMBINE(syllable.middleVowelComponent()); STKS_COMBINE(syllable.vowelComponent()); @@ -220,256 +268,314 @@ if ((k = componentToKey(c))) sequence += std::string(1, k); \ #undef STKS_COMBINE return sequence; } - - const BPMF syllableFromKeySequence(const std::string& sequence) const { + + const BPMF syllableFromKeySequence(const std::string &sequence) const + { BPMF syllable; - - for (std::string::const_iterator iter = sequence.begin(); - iter != sequence.end(); ++iter) { + + for (std::string::const_iterator iter = sequence.begin(); iter != sequence.end(); ++iter) + { bool beforeSeqHasIorUE = sequenceContainsIorUE(sequence.begin(), iter); bool aheadSeqHasIorUE = sequenceContainsIorUE(iter + 1, sequence.end()); - + std::vector components = keyToComponents(*iter); - - if (!components.size()) continue; - - if (components.size() == 1) { + + if (!components.size()) + continue; + + if (components.size() == 1) + { syllable += BPMF(components[0]); continue; } - + BPMF head = BPMF(components[0]); BPMF follow = BPMF(components[1]); BPMF ending = components.size() > 2 ? BPMF(components[2]) : follow; - + // apply the I/UE + E rule - if (head.vowelComponent() == BPMF::E && - follow.vowelComponent() != BPMF::E) { + if (head.vowelComponent() == BPMF::E && follow.vowelComponent() != BPMF::E) + { syllable += beforeSeqHasIorUE ? head : follow; continue; } - - if (head.vowelComponent() != BPMF::E && - follow.vowelComponent() == BPMF::E) { + + if (head.vowelComponent() != BPMF::E && follow.vowelComponent() == BPMF::E) + { syllable += beforeSeqHasIorUE ? follow : head; continue; } - + // apply the J/Q/X + I/UE rule, only two components are allowed in the // components vector here - if (head.belongsToJQXClass() && !follow.belongsToJQXClass()) { - if (!syllable.isEmpty()) { - if (ending != follow) syllable += ending; - } else { + if (head.belongsToJQXClass() && !follow.belongsToJQXClass()) + { + if (!syllable.isEmpty()) + { + if (ending != follow) + syllable += ending; + } + else + { syllable += aheadSeqHasIorUE ? head : follow; } - + continue; } - - if (!head.belongsToJQXClass() && follow.belongsToJQXClass()) { - if (!syllable.isEmpty()) { - if (ending != follow) syllable += ending; - } else { + + if (!head.belongsToJQXClass() && follow.belongsToJQXClass()) + { + if (!syllable.isEmpty()) + { + if (ending != follow) + syllable += ending; + } + else + { syllable += aheadSeqHasIorUE ? follow : head; } - + continue; } - + // the nasty issue of only one char in the buffer - if (iter == sequence.begin() && iter + 1 == sequence.end()) { - if (head.hasVowel() || follow.hasToneMarker() || - head.belongsToZCSRClass()) { + if (iter == sequence.begin() && iter + 1 == sequence.end()) + { + if (head.hasVowel() || follow.hasToneMarker() || head.belongsToZCSRClass()) + { syllable += head; - } else { - if (follow.hasVowel() || ending.hasToneMarker()) { + } + else + { + if (follow.hasVowel() || ending.hasToneMarker()) + { syllable += follow; - } else { + } + else + { syllable += ending; } } - + continue; } - - if (!(syllable.maskType() & head.maskType()) && - !endAheadOrAheadHasToneMarkKey(iter + 1, sequence.end())) { + + if (!(syllable.maskType() & head.maskType()) && !endAheadOrAheadHasToneMarkKey(iter + 1, sequence.end())) + { syllable += head; - } else { - if (endAheadOrAheadHasToneMarkKey(iter + 1, sequence.end()) && - head.belongsToZCSRClass() && syllable.isEmpty()) { + } + else + { + if (endAheadOrAheadHasToneMarkKey(iter + 1, sequence.end()) && head.belongsToZCSRClass() && + syllable.isEmpty()) + { syllable += head; - } else if (syllable.maskType() < follow.maskType()) { + } + else if (syllable.maskType() < follow.maskType()) + { syllable += follow; - } else { + } + else + { syllable += ending; } } } - + // heuristics for Hsu keyboard layout - if (this == HsuLayout()) { + if (this == HsuLayout()) + { // fix the left out L to ERR when it has sound, and GI, GUE -> JI, JUE - if (syllable.vowelComponent() == BPMF::ENG && !syllable.hasConsonant() && - !syllable.hasMiddleVowel()) { + if (syllable.vowelComponent() == BPMF::ENG && !syllable.hasConsonant() && !syllable.hasMiddleVowel()) + { syllable += BPMF(BPMF::ERR); - } else if (syllable.consonantComponent() == BPMF::G && - (syllable.middleVowelComponent() == BPMF::I || - syllable.middleVowelComponent() == BPMF::UE)) { + } + else if (syllable.consonantComponent() == BPMF::G && + (syllable.middleVowelComponent() == BPMF::I || syllable.middleVowelComponent() == BPMF::UE)) + { syllable += BPMF(BPMF::J); } } - + return syllable; } - -protected: - bool endAheadOrAheadHasToneMarkKey(std::string::const_iterator ahead, - std::string::const_iterator end) const { - if (ahead == end) return true; - + + protected: + bool endAheadOrAheadHasToneMarkKey(std::string::const_iterator ahead, std::string::const_iterator end) const + { + if (ahead == end) + return true; + char tone1 = componentToKey(BPMF::Tone1); char tone2 = componentToKey(BPMF::Tone2); char tone3 = componentToKey(BPMF::Tone3); char tone4 = componentToKey(BPMF::Tone4); char tone5 = componentToKey(BPMF::Tone5); - + if (tone1) - if (*ahead == tone1) return true; - - if (*ahead == tone2 || *ahead == tone3 || *ahead == tone4 || - *ahead == tone5) + if (*ahead == tone1) + return true; + + if (*ahead == tone2 || *ahead == tone3 || *ahead == tone4 || *ahead == tone5) return true; - + return false; } - - bool sequenceContainsIorUE(std::string::const_iterator start, - std::string::const_iterator end) const { + + bool sequenceContainsIorUE(std::string::const_iterator start, std::string::const_iterator end) const + { char iChar = componentToKey(BPMF::I); char ueChar = componentToKey(BPMF::UE); - + for (; start != end; ++start) - if (*start == iChar || *start == ueChar) return true; + if (*start == iChar || *start == ueChar) + return true; return false; } - + std::string m_name; BopomofoKeyToComponentMap m_keyToComponent; BopomofoComponentToKeyMap m_componentToKey; }; -class BopomofoReadingBuffer { -public: - explicit BopomofoReadingBuffer(const BopomofoKeyboardLayout* layout) - : layout_(layout), pinyin_mode_(false) { - if (layout == BopomofoKeyboardLayout::HanyuPinyinLayout()) { +class BopomofoReadingBuffer +{ + public: + explicit BopomofoReadingBuffer(const BopomofoKeyboardLayout *layout) : layout_(layout), pinyin_mode_(false) + { + if (layout == BopomofoKeyboardLayout::HanyuPinyinLayout()) + { pinyin_mode_ = true; pinyin_sequence_ = ""; } } - - void setKeyboardLayout(const BopomofoKeyboardLayout* layout) { + + void setKeyboardLayout(const BopomofoKeyboardLayout *layout) + { layout_ = layout; - - if (layout == BopomofoKeyboardLayout::HanyuPinyinLayout()) { + + if (layout == BopomofoKeyboardLayout::HanyuPinyinLayout()) + { pinyin_mode_ = true; pinyin_sequence_ = ""; } } - - bool isValidKey(char k) const { - if (!pinyin_mode_) { + + bool isValidKey(char k) const + { + if (!pinyin_mode_) + { return layout_ ? (layout_->keyToComponents(k)).size() > 0 : false; } - + char lk = tolower(k); - if (lk >= 'a' && lk <= 'z') { + if (lk >= 'a' && lk <= 'z') + { // if a tone marker is already in place - if (pinyin_sequence_.length()) { + if (pinyin_sequence_.length()) + { char lastc = pinyin_sequence_[pinyin_sequence_.length() - 1]; - if (lastc >= '2' && lastc <= '5') { + if (lastc >= '2' && lastc <= '5') + { return false; } return true; } return true; } - - if (pinyin_sequence_.length() && (lk >= '2' && lk <= '5')) { + + if (pinyin_sequence_.length() && (lk >= '2' && lk <= '5')) + { return true; } - + return false; } - - bool combineKey(char k) { - if (!isValidKey(k)) return false; - - if (pinyin_mode_) { + + bool combineKey(char k) + { + if (!isValidKey(k)) + return false; + + if (pinyin_mode_) + { pinyin_sequence_ += std::string(1, tolower(k)); syllable_ = BPMF::FromHanyuPinyin(pinyin_sequence_); return true; } - - std::string sequence = - layout_->keySequenceFromSyllable(syllable_) + std::string(1, k); + + std::string sequence = layout_->keySequenceFromSyllable(syllable_) + std::string(1, k); syllable_ = layout_->syllableFromKeySequence(sequence); return true; } - - void clear() { + + void clear() + { pinyin_sequence_.clear(); syllable_.clear(); } - - void backspace() { - if (!layout_) return; - - if (pinyin_mode_) { - if (pinyin_sequence_.length()) { - pinyin_sequence_ = - pinyin_sequence_.substr(0, pinyin_sequence_.length() - 1); + + void backspace() + { + if (!layout_) + return; + + if (pinyin_mode_) + { + if (pinyin_sequence_.length()) + { + pinyin_sequence_ = pinyin_sequence_.substr(0, pinyin_sequence_.length() - 1); } - + syllable_ = BPMF::FromHanyuPinyin(pinyin_sequence_); return; } - + std::string sequence = layout_->keySequenceFromSyllable(syllable_); - if (sequence.length()) { + if (sequence.length()) + { sequence = sequence.substr(0, sequence.length() - 1); syllable_ = layout_->syllableFromKeySequence(sequence); } } - - bool isEmpty() const { return syllable_.isEmpty(); } - - const std::string composedString() const { - if (pinyin_mode_) { + + bool isEmpty() const + { + return syllable_.isEmpty(); + } + + const std::string composedString() const + { + if (pinyin_mode_) + { return pinyin_sequence_; } - + return syllable_.composedString(); } - - const BPMF syllable() const { return syllable_; } - - const std::string standardLayoutQueryString() const { + + const BPMF syllable() const + { + return syllable_; + } + + const std::string standardLayoutQueryString() const + { return BopomofoKeyboardLayout::StandardLayout()->keySequenceFromSyllable(syllable_); } - - bool hasToneMarker() const { return syllable_.hasToneMarker(); } - -protected: - const BopomofoKeyboardLayout* layout_; + + bool hasToneMarker() const + { + return syllable_.hasToneMarker(); + } + + protected: + const BopomofoKeyboardLayout *layout_; BPMF syllable_; - + bool pinyin_mode_; std::string pinyin_sequence_; }; -} // namespace Mandarin +} // namespace Mandarin - -#endif // MANDARIN_H_ +#endif // MANDARIN_H_ diff --git a/Source/Headers/vChewing-Bridging-Header.h b/Source/Headers/vChewing-Bridging-Header.h index d78ca431..3f79ac94 100644 --- a/Source/Headers/vChewing-Bridging-Header.h +++ b/Source/Headers/vChewing-Bridging-Header.h @@ -1,20 +1,27 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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. */ // diff --git a/Source/Headers/vChewing-Prefix.pch b/Source/Headers/vChewing-Prefix.pch index 19be19bb..292aa8dd 100644 --- a/Source/Headers/vChewing-Prefix.pch +++ b/Source/Headers/vChewing-Prefix.pch @@ -1,20 +1,27 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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. */ // diff --git a/Source/Modules/ControllerModules/KeyHandler.h b/Source/Modules/ControllerModules/KeyHandler.h index 0b3c4cef..6b6116d0 100644 --- a/Source/Modules/ControllerModules/KeyHandler.h +++ b/Source/Modules/ControllerModules/KeyHandler.h @@ -1,20 +1,27 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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 @@ -33,7 +40,9 @@ extern InputMode imeModeNULL; @protocol KeyHandlerDelegate - (id)candidateControllerForKeyHandler:(KeyHandler *)keyHandler; -- (void)keyHandler:(KeyHandler *)keyHandler didSelectCandidateAtIndex:(NSInteger)index candidateController:(id)controller; +- (void)keyHandler:(KeyHandler *)keyHandler + didSelectCandidateAtIndex:(NSInteger)index + candidateController:(id)controller; - (BOOL)keyHandler:(KeyHandler *)keyHandler didRequestWriteUserPhraseWithState:(InputState *)state; @end @@ -43,7 +52,8 @@ extern InputMode imeModeNULL; - (BOOL)handleInput:(keyParser *)input state:(InputState *)state stateCallback:(void (^)(InputState *))stateCallback - errorCallback:(void (^)(void))errorCallback NS_SWIFT_NAME(handle(input:state:stateCallback:errorCallback:)); + errorCallback:(void (^)(void))errorCallback + NS_SWIFT_NAME(handle(input:state:stateCallback:errorCallback:)); - (void)syncWithPreferences; - (void)fixNodeWithValue:(NSString *)value NS_SWIFT_NAME(fixNode(value:)); @@ -52,8 +62,8 @@ extern InputMode imeModeNULL; - (InputState *)buildInputtingState; - (nullable InputState *)buildAssociatePhraseStateWithKey:(NSString *)key useVerticalMode:(BOOL)useVerticalMode; -@property (strong, nonatomic) InputMode inputMode; -@property (weak, nonatomic) id delegate; +@property(strong, nonatomic) InputMode inputMode; +@property(weak, nonatomic) id delegate; @end NS_ASSUME_NONNULL_END diff --git a/Source/Modules/ControllerModules/KeyHandler.mm b/Source/Modules/ControllerModules/KeyHandler.mm index 0509a9cb..c2eb3b12 100644 --- a/Source/Modules/ControllerModules/KeyHandler.mm +++ b/Source/Modules/ControllerModules/KeyHandler.mm @@ -1,29 +1,35 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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 "Gramambular.h" #import "KeyHandler.h" +#import "Gramambular.h" #import "LMInstantiator.h" #import "Mandarin.h" -#import "mgrLangModel_Privates.h" #import "UserOverrideModel.h" +#import "mgrLangModel_Privates.h" #import "vChewing-Swift.h" #import @@ -33,32 +39,35 @@ InputMode imeModeNULL = ctlInputMethod.kIMEModeNULL; static const double kEpsilon = 0.000001; -static double FindHighestScore(const std::vector &nodes, double epsilon) { +static double FindHighestScore(const std::vector &nodes, double epsilon) +{ double highestScore = 0.0; - for (auto ni = nodes.begin(), ne = nodes.end(); ni != ne; ++ni) { + for (auto ni = nodes.begin(), ne = nodes.end(); ni != ne; ++ni) + { double score = ni->node->highestUnigramScore(); - if (score > highestScore) { + if (score > highestScore) + { highestScore = score; } } return highestScore + epsilon; } -class NodeAnchorDescendingSorter { -public: +class NodeAnchorDescendingSorter +{ + public: bool operator()(const Gramambular::NodeAnchor &a, const Gramambular::NodeAnchor &b) const { return a.node->key().length() > b.node->key().length(); } }; -;// if DEBUG is defined, a DOT file (GraphViz format) will be written to the +// if DEBUG is defined, a DOT file (GraphViz format) will be written to the // specified path every time the grid is walked #if DEBUG static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; #endif - @implementation KeyHandler { // the reading buffer that takes user input @@ -87,10 +96,14 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; return _inputMode; } -- (BOOL)isBuilderEmpty { - if (_builder->grid().width() == 0) { +- (BOOL)isBuilderEmpty +{ + if (_builder->grid().width() == 0) + { return YES; - } else { + } + else + { return NO; } } @@ -101,11 +114,14 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; vChewing::LMInstantiator *newLanguageModel; vChewing::UserOverrideModel *newUserOverrideModel; - if ([value isKindOfClass:[NSString class]] && [value isEqual:imeModeCHS]) { + if ([value isKindOfClass:[NSString class]] && [value isEqual:imeModeCHS]) + { newInputMode = imeModeCHS; newLanguageModel = [mgrLangModel lmCHS]; newUserOverrideModel = [mgrLangModel userOverrideModelCHS]; - } else { + } + else + { newInputMode = imeModeCHT; newLanguageModel = [mgrLangModel lmCHT]; newUserOverrideModel = [mgrLangModel userOverrideModelCHT]; @@ -121,18 +137,21 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; newLanguageModel->setCNSEnabled(mgrPrefs.cns11643Enabled); // Only apply the changes if the value is changed - if (![_inputMode isEqualToString:newInputMode]) { + if (![_inputMode isEqualToString:newInputMode]) + { _inputMode = newInputMode; _languageModel = newLanguageModel; _userOverrideModel = newUserOverrideModel; - if (_builder) { + if (_builder) + { delete _builder; _builder = new Gramambular::BlockReadingBuilder(_languageModel); _builder->setJoinSeparator("-"); } - if (!_bpmfReadingBuffer->isEmpty()) { + if (!_bpmfReadingBuffer->isEmpty()) + { _bpmfReadingBuffer->clear(); } } @@ -141,11 +160,13 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; - (void)dealloc { // clean up everything - if (_bpmfReadingBuffer) { + if (_bpmfReadingBuffer) + { delete _bpmfReadingBuffer; } - if (_builder) { + if (_builder) + { delete _builder; } } @@ -153,7 +174,8 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; - (instancetype)init { self = [super init]; - if (self) { + if (self) + { _bpmfReadingBuffer = new Mandarin::BopomofoReadingBuffer(Mandarin::BopomofoKeyboardLayout::StandardLayout()); // create the lattice builder @@ -164,7 +186,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; _userOverrideModel = [mgrLangModel userOverrideModelCHT]; _builder = new Gramambular::BlockReadingBuilder(_languageModel); - + // each Mandarin syllable is separated by a hyphen _builder->setJoinSeparator("-"); _inputMode = imeModeCHT; @@ -175,34 +197,35 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; - (void)syncWithPreferences { NSInteger layout = mgrPrefs.keyboardLayout; - switch (layout) { - case KeyboardLayoutOfStandard: - _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::StandardLayout()); - break; - case KeyboardLayoutOfEten: - _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::ETenLayout()); - break; - case KeyboardLayoutOfHsu: - _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::HsuLayout()); - break; - case KeyboardLayoutOfEen26: - _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::ETen26Layout()); - break; - case KeyboardLayoutOfIBM: - _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::IBMLayout()); - break; - case KeyboardLayoutOfMiTAC: - _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::MiTACLayout()); - break; - case KeyboardLayoutOfFakeSeigyou: - _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::FakeSeigyouLayout()); - break; - case KeyboardLayoutOfHanyuPinyin: - _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::HanyuPinyinLayout()); - break; - default: - _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::StandardLayout()); - mgrPrefs.keyboardLayout = KeyboardLayoutOfStandard; + switch (layout) + { + case KeyboardLayoutOfStandard: + _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::StandardLayout()); + break; + case KeyboardLayoutOfEten: + _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::ETenLayout()); + break; + case KeyboardLayoutOfHsu: + _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::HsuLayout()); + break; + case KeyboardLayoutOfEen26: + _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::ETen26Layout()); + break; + case KeyboardLayoutOfIBM: + _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::IBMLayout()); + break; + case KeyboardLayoutOfMiTAC: + _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::MiTACLayout()); + break; + case KeyboardLayoutOfFakeSeigyou: + _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::FakeSeigyouLayout()); + break; + case KeyboardLayoutOfHanyuPinyin: + _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::HanyuPinyinLayout()); + break; + default: + _bpmfReadingBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::StandardLayout()); + mgrPrefs.keyboardLayout = KeyboardLayoutOfStandard; } } @@ -211,35 +234,44 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; size_t cursorIndex = [self _actualCandidateCursorIndex]; std::string stringValue(value.UTF8String); Gramambular::NodeAnchor selectedNode = _builder->grid().fixNodeSelectedCandidate(cursorIndex, stringValue); - if (!mgrPrefs.useSCPCTypingMode) { // 不要針對逐字選字模式啟用臨時半衰記憶模型。 + if (!mgrPrefs.useSCPCTypingMode) + { // 不要針對逐字選字模式啟用臨時半衰記憶模型。 // If the length of the readings and the characters do not match, // it often means it is a special symbol and it should not be stored // in the user override model. BOOL addToOverrideModel = YES; - if (selectedNode.spanningLength != [value count]) { + if (selectedNode.spanningLength != [value count]) + { addToOverrideModel = NO; } - if (addToOverrideModel) { + if (addToOverrideModel) + { double score = selectedNode.node->scoreForCandidate(stringValue); - if (score <= -12) { // 威注音的 SymbolLM 的 Score 是 -12。 + if (score <= -12) + { // 威注音的 SymbolLM 的 Score 是 -12。 addToOverrideModel = NO; } } - if (addToOverrideModel) { + if (addToOverrideModel) + { _userOverrideModel->observe(_walkedNodes, cursorIndex, stringValue, [[NSDate date] timeIntervalSince1970]); } } [self _walk]; - if (mgrPrefs.moveCursorAfterSelectingCandidate) { + if (mgrPrefs.moveCursorAfterSelectingCandidate) + { size_t nextPosition = 0; - for (auto node: _walkedNodes) { - if (nextPosition >= cursorIndex) { + for (auto node : _walkedNodes) + { + if (nextPosition >= cursorIndex) + { break; } nextPosition += node.spanningLength; } - if (nextPosition <= _builder->length()) { + if (nextPosition <= _builder->length()) + { _builder->setCursorIndex(nextPosition); } } @@ -259,59 +291,76 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; return layout; } -- (BOOL)handleInput:(keyParser *)input state:(InputState *)inState stateCallback:(void (^)(InputState *))stateCallback errorCallback:(void (^)(void))errorCallback +- (BOOL)handleInput:(keyParser *)input + state:(InputState *)inState + stateCallback:(void (^)(InputState *))stateCallback + errorCallback:(void (^)(void))errorCallback { InputState *state = inState; UniChar charCode = input.charCode; vChewingEmacsKey emacsKey = input.emacsKey; // if the inputText is empty, it's a function key combination, we ignore it - if (!input.inputText.length) { + if (!input.inputText.length) + { return NO; } // if the composing buffer is empty and there's no reading, and there is some function key combination, we ignore it - BOOL isFunctionKey = ([input isCommandHold] || [input isOptionHotKey] || [input isNumericPad]) || [input isControlHotKey]; + BOOL isFunctionKey = + ([input isCommandHold] || [input isOptionHotKey] || [input isNumericPad]) || [input isControlHotKey]; if (![state isKindOfClass:[InputStateNotEmpty class]] && - ![state isKindOfClass:[InputStateAssociatedPhrases class]] && - isFunctionKey) { + ![state isKindOfClass:[InputStateAssociatedPhrases class]] && isFunctionKey) + { return NO; } // Caps Lock processing: if Caps Lock is ON, temporarily disable bopomofo. // Note: Alphanumerical mode processing. - if ([input isBackSpace] || [input isEnter] || [input isAbsorbedArrowKey] || [input isExtraChooseCandidateKey] || [input isExtraChooseCandidateKeyReverse] || [input isCursorForward] || [input isCursorBackward]) { + if ([input isBackSpace] || [input isEnter] || [input isAbsorbedArrowKey] || [input isExtraChooseCandidateKey] || + [input isExtraChooseCandidateKeyReverse] || [input isCursorForward] || [input isCursorBackward]) + { // do nothing if backspace is pressed -- we ignore the key - } else if ([input isCapsLockOn]) { + } + else if ([input isCapsLockOn]) + { // process all possible combination, we hope. [self clear]; InputStateEmpty *emptyState = [[InputStateEmpty alloc] init]; stateCallback(emptyState); // When shift is pressed, don't do further processing, since it outputs capital letter anyway. - if ([input isShiftHold]) { + if ([input isShiftHold]) + { return NO; } - // if ASCII but not printable, don't use insertText:replacementRange: as many apps don't handle non-ASCII char insertions. - if (charCode < 0x80 && !isprint(charCode)) { + // if ASCII but not printable, don't use insertText:replacementRange: as many apps don't handle non-ASCII char + // insertions. + if (charCode < 0x80 && !isprint(charCode)) + { return NO; } // commit everything in the buffer. - InputStateCommitting *committingState = [[InputStateCommitting alloc] initWithPoppedText:[input.inputText lowercaseString]]; + InputStateCommitting *committingState = + [[InputStateCommitting alloc] initWithPoppedText:[input.inputText lowercaseString]]; stateCallback(committingState); stateCallback(emptyState); return YES; } - if ([input isNumericPad]) { - if (![input isLeft] && ![input isRight] && ![input isDown] && ![input isUp] && ![input isSpace] && isprint(charCode)) { + if ([input isNumericPad]) + { + if (![input isLeft] && ![input isRight] && ![input isDown] && ![input isUp] && ![input isSpace] && + isprint(charCode)) + { [self clear]; InputStateEmpty *emptyState = [[InputStateEmpty alloc] init]; stateCallback(emptyState); - InputStateCommitting *committing = [[InputStateCommitting alloc] initWithPoppedText:[input.inputText lowercaseString]]; + InputStateCommitting *committing = + [[InputStateCommitting alloc] initWithPoppedText:[input.inputText lowercaseString]]; stateCallback(committing); stateCallback(emptyState); return YES; @@ -319,14 +368,20 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; } // MARK: Handle Candidates - if ([state isKindOfClass:[InputStateChoosingCandidate class]]) { + if ([state isKindOfClass:[InputStateChoosingCandidate class]]) + { return [self _handleCandidateState:state input:input stateCallback:stateCallback errorCallback:errorCallback]; } // MARK: Handle Associated Phrases - if ([state isKindOfClass:[InputStateAssociatedPhrases class]]) { - BOOL result = [self _handleCandidateState:state input:input stateCallback:stateCallback errorCallback:errorCallback]; - if (result) { + if ([state isKindOfClass:[InputStateAssociatedPhrases class]]) + { + BOOL result = [self _handleCandidateState:state + input:input + stateCallback:stateCallback + errorCallback:errorCallback]; + if (result) + { return YES; } state = [[InputStateEmpty alloc] init]; @@ -334,9 +389,14 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; } // MARK: Handle Marking - if ([state isKindOfClass:[InputStateMarking class]]) { - InputStateMarking *marking = (InputStateMarking *) state; - if ([self _handleMarkingState:(InputStateMarking *) state input:input stateCallback:stateCallback errorCallback:errorCallback]) { + if ([state isKindOfClass:[InputStateMarking class]]) + { + InputStateMarking *marking = (InputStateMarking *)state; + if ([self _handleMarkingState:(InputStateMarking *)state + input:input + stateCallback:stateCallback + errorCallback:errorCallback]) + { return YES; } state = [marking convertToInputting]; @@ -349,14 +409,16 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; // MARK: Handle BPMF Keys // see if it's valid BPMF reading - if (!skipBpmfHandling && _bpmfReadingBuffer->isValidKey((char) charCode)) { - _bpmfReadingBuffer->combineKey((char) charCode); + if (!skipBpmfHandling && _bpmfReadingBuffer->isValidKey((char)charCode)) + { + _bpmfReadingBuffer->combineKey((char)charCode); // if we have a tone marker, we have to insert the reading to the // builder in other words, if we don't have a tone marker, we just // update the composing buffer composeReading = _bpmfReadingBuffer->hasToneMarker(); - if (!composeReading) { + if (!composeReading) + { InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; stateCallback(inputting); return YES; @@ -366,12 +428,14 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; // see if we have composition if Enter/Space is hit and buffer is not empty // this is bit-OR'ed so that the tone marker key is also taken into account composeReading |= (!_bpmfReadingBuffer->isEmpty() && ([input isSpace] || [input isEnter])); - if (composeReading) { + if (composeReading) + { // combine the reading std::string reading = _bpmfReadingBuffer->syllable().composedString(); // see if we have a unigram for this - if (!_languageModel->hasUnigramsForKey(reading)) { + if (!_languageModel->hasUnigramsForKey(reading)) + { [IME prtDebugIntel:@"B49C0979"]; errorCallback(); InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; @@ -386,14 +450,18 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; NSString *poppedText = [self _popOverflowComposingTextAndWalk]; // get user override model suggestion - std::string overrideValue = (mgrPrefs.useSCPCTypingMode) ? "" : - _userOverrideModel->suggest(_walkedNodes, _builder->cursorIndex(), [[NSDate date] timeIntervalSince1970]); + std::string overrideValue = (mgrPrefs.useSCPCTypingMode) + ? "" + : _userOverrideModel->suggest(_walkedNodes, _builder->cursorIndex(), + [[NSDate date] timeIntervalSince1970]); - if (!overrideValue.empty()) { + if (!overrideValue.empty()) + { size_t cursorIndex = [self _actualCandidateCursorIndex]; std::vector nodes = _builder->grid().nodesCrossingOrEndingAt(cursorIndex); double highestScore = FindHighestScore(nodes, kEpsilon); - _builder->grid().overrideNodeScoreForSelectedCandidate(cursorIndex, overrideValue, static_cast(highestScore)); + _builder->grid().overrideNodeScoreForSelectedCandidate(cursorIndex, overrideValue, + static_cast(highestScore)); } // then update the text @@ -403,27 +471,40 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; inputting.poppedText = poppedText; stateCallback(inputting); - if (mgrPrefs.useSCPCTypingMode) { - InputStateChoosingCandidate *choosingCandidates = [self _buildCandidateState:inputting useVerticalMode:input.useVerticalMode]; - if (choosingCandidates.candidates.count == 1) { + if (mgrPrefs.useSCPCTypingMode) + { + InputStateChoosingCandidate *choosingCandidates = [self _buildCandidateState:inputting + useVerticalMode:input.useVerticalMode]; + if (choosingCandidates.candidates.count == 1) + { [self clear]; NSString *text = choosingCandidates.candidates.firstObject; InputStateCommitting *committing = [[InputStateCommitting alloc] initWithPoppedText:text]; stateCallback(committing); - if (!mgrPrefs.associatedPhrasesEnabled) { + if (!mgrPrefs.associatedPhrasesEnabled) + { InputStateEmpty *empty = [[InputStateEmpty alloc] init]; stateCallback(empty); - } else { - InputStateAssociatedPhrases *associatedPhrases = (InputStateAssociatedPhrases *)[self buildAssociatePhraseStateWithKey:text useVerticalMode:input.useVerticalMode]; - if (associatedPhrases) { + } + else + { + InputStateAssociatedPhrases *associatedPhrases = + (InputStateAssociatedPhrases *)[self buildAssociatePhraseStateWithKey:text + useVerticalMode:input.useVerticalMode]; + if (associatedPhrases) + { stateCallback(associatedPhrases); - } else { + } + else + { InputStateEmpty *empty = [[InputStateEmpty alloc] init]; stateCallback(empty); } } - } else { + } + else + { stateCallback(choosingCandidates); } } @@ -433,26 +514,33 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; } // MARK: Calling candidate window using Space or Down or PageUp / PageDn. - if (_bpmfReadingBuffer->isEmpty() && - [state isKindOfClass:[InputStateNotEmpty class]] && - ([input isExtraChooseCandidateKey] || [input isExtraChooseCandidateKeyReverse] - || [input isSpace] || [input isPageDown] || [input isPageUp] || [input isTab] - || (input.useVerticalMode && ([input isVerticalModeOnlyChooseCandidateKey])))) { - if ([input isSpace]) { + if (_bpmfReadingBuffer->isEmpty() && [state isKindOfClass:[InputStateNotEmpty class]] && + ([input isExtraChooseCandidateKey] || [input isExtraChooseCandidateKeyReverse] || [input isSpace] || + [input isPageDown] || [input isPageUp] || [input isTab] || + (input.useVerticalMode && ([input isVerticalModeOnlyChooseCandidateKey])))) + { + if ([input isSpace]) + { // if the spacebar is NOT set to be a selection key - if ([input isShiftHold] || !mgrPrefs.chooseCandidateUsingSpace) { - if (_builder->cursorIndex() >= _builder->length()) { - NSString *composingBuffer = [(InputStateNotEmpty*) state composingBuffer]; - if (composingBuffer.length) { - InputStateCommitting *committing = [[InputStateCommitting alloc] initWithPoppedText:composingBuffer]; - stateCallback (committing); + if ([input isShiftHold] || !mgrPrefs.chooseCandidateUsingSpace) + { + if (_builder->cursorIndex() >= _builder->length()) + { + NSString *composingBuffer = [(InputStateNotEmpty *)state composingBuffer]; + if (composingBuffer.length) + { + InputStateCommitting *committing = + [[InputStateCommitting alloc] initWithPoppedText:composingBuffer]; + stateCallback(committing); } [self clear]; InputStateCommitting *committing = [[InputStateCommitting alloc] initWithPoppedText:@" "]; stateCallback(committing); InputStateEmpty *empty = [[InputStateEmpty alloc] init]; stateCallback(empty); - } else if (_languageModel->hasUnigramsForKey(" ")) { + } + else if (_languageModel->hasUnigramsForKey(" ")) + { _builder->insertReadingAtCursor(" "); NSString *poppedText = [self _popOverflowComposingTextAndWalk]; InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; @@ -460,141 +548,187 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; stateCallback(inputting); } return YES; - } } - InputStateChoosingCandidate *choosingCandidates = [self _buildCandidateState:(InputStateNotEmpty *) state useVerticalMode:input.useVerticalMode]; + InputStateChoosingCandidate *choosingCandidates = [self _buildCandidateState:(InputStateNotEmpty *)state + useVerticalMode:input.useVerticalMode]; stateCallback(choosingCandidates); return YES; } // MARK: Esc - if ([input isESC]) { + if ([input isESC]) + { return [self _handleEscWithState:state stateCallback:stateCallback errorCallback:errorCallback]; } // MARK: Cursor backward - if ([input isCursorBackward] || emacsKey == vChewingEmacsKeyBackward) { - return [self _handleBackwardWithState:state input:input stateCallback:stateCallback errorCallback:errorCallback]; + if ([input isCursorBackward] || emacsKey == vChewingEmacsKeyBackward) + { + return [self _handleBackwardWithState:state + input:input + stateCallback:stateCallback + errorCallback:errorCallback]; } // MARK: Cursor forward - if ([input isCursorForward] || emacsKey == vChewingEmacsKeyForward) { + if ([input isCursorForward] || emacsKey == vChewingEmacsKeyForward) + { return [self _handleForwardWithState:state input:input stateCallback:stateCallback errorCallback:errorCallback]; } // MARK: Home - if ([input isHome] || emacsKey == vChewingEmacsKeyHome) { + if ([input isHome] || emacsKey == vChewingEmacsKeyHome) + { return [self _handleHomeWithState:state stateCallback:stateCallback errorCallback:errorCallback]; } // MARK: End - if ([input isEnd] || emacsKey == vChewingEmacsKeyEnd) { + if ([input isEnd] || emacsKey == vChewingEmacsKeyEnd) + { return [self _handleEndWithState:state stateCallback:stateCallback errorCallback:errorCallback]; } // MARK: Ctrl+PgLf or Shift+PgLf - if ([input isControlHold] || [input isShiftHold]) { - if ([input isOptionHold] && [input isLeft]) { + if ([input isControlHold] || [input isShiftHold]) + { + if ([input isOptionHold] && [input isLeft]) + { return [self _handleHomeWithState:state stateCallback:stateCallback errorCallback:errorCallback]; } } // MARK: Ctrl+PgRt or Shift+PgRt - if ([input isControlHold] || [input isShiftHold]) { - if ([input isOptionHold] && [input isRight]) { + if ([input isControlHold] || [input isShiftHold]) + { + if ([input isOptionHold] && [input isRight]) + { return [self _handleEndWithState:state stateCallback:stateCallback errorCallback:errorCallback]; } } // MARK: AbsorbedArrowKey - if ([input isAbsorbedArrowKey] || [input isExtraChooseCandidateKey] || [input isExtraChooseCandidateKeyReverse]) { + if ([input isAbsorbedArrowKey] || [input isExtraChooseCandidateKey] || [input isExtraChooseCandidateKeyReverse]) + { return [self _handleAbsorbedArrowKeyWithState:state stateCallback:stateCallback errorCallback:errorCallback]; } // MARK: Backspace - if ([input isBackSpace]) { + if ([input isBackSpace]) + { return [self _handleBackspaceWithState:state stateCallback:stateCallback errorCallback:errorCallback]; } // MARK: Delete - if ([input isDelete] || emacsKey == vChewingEmacsKeyDelete) { + if ([input isDelete] || emacsKey == vChewingEmacsKeyDelete) + { return [self _handleDeleteWithState:state stateCallback:stateCallback errorCallback:errorCallback]; } // MARK: Enter - if ([input isEnter]) { - return ([input isControlHold] && [input isCommandHold]) ? - [self _handleCtrlCommandEnterWithState:state stateCallback:stateCallback errorCallback:errorCallback] - : - [self _handleEnterWithState:state stateCallback:stateCallback errorCallback:errorCallback]; + if ([input isEnter]) + { + return ([input isControlHold] && [input isCommandHold]) + ? [self _handleCtrlCommandEnterWithState:state + stateCallback:stateCallback + errorCallback:errorCallback] + : [self _handleEnterWithState:state stateCallback:stateCallback errorCallback:errorCallback]; } // MARK: Punctuation list - if ([input isSymbolMenuPhysicalKey] && ![input isShiftHold]) { + if ([input isSymbolMenuPhysicalKey] && ![input isShiftHold]) + { // 得在這裡先 commit buffer,不然會導致「在摁 ESC 離開符號選單時會重複輸入上一次的組字區的內容」的不當行為。 // 於是這裡用「模擬一次 Enter 鍵的操作」使其代為執行這個 commit buffer 的動作。 [self _handleEnterWithState:state stateCallback:stateCallback errorCallback:errorCallback]; SymbolNode *root = [SymbolNode root]; - InputStateSymbolTable *symbolState = [[InputStateSymbolTable alloc] initWithNode:root useVerticalMode:input.useVerticalMode]; + InputStateSymbolTable *symbolState = [[InputStateSymbolTable alloc] initWithNode:root + useVerticalMode:input.useVerticalMode]; stateCallback(symbolState); return YES; -// if (_languageModel->hasUnigramsForKey("_punctuation_list"))) { -// if (_bpmfReadingBuffer->isEmpty()) { -// _builder->insertReadingAtCursor(string("_punctuation_list")); -// NSString *poppedText = [self _popOverflowComposingTextAndWalk]; -// InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; -// inputting.poppedText = poppedText; -// stateCallback(inputting); -// InputStateChoosingCandidate *choosingCandidate = [self _buildCandidateState:inputting useVerticalMode:input.useVerticalMode]; -// stateCallback(choosingCandidate); -// } else { // If there is still unfinished bpmf reading, ignore the punctuation -// errorCallback(); -// } -// return YES; -// } + // if (_languageModel->hasUnigramsForKey("_punctuation_list"))) { + // if (_bpmfReadingBuffer->isEmpty()) { + // _builder->insertReadingAtCursor(string("_punctuation_list")); + // NSString *poppedText = [self _popOverflowComposingTextAndWalk]; + // InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; + // inputting.poppedText = poppedText; + // stateCallback(inputting); + // InputStateChoosingCandidate *choosingCandidate = [self _buildCandidateState:inputting + // useVerticalMode:input.useVerticalMode]; stateCallback(choosingCandidate); + // } else { // If there is still unfinished bpmf reading, ignore the punctuation + // errorCallback(); + // } + // return YES; + // } } // MARK: Punctuation // if nothing is matched, see if it's a punctuation key for current layout. std::string punctuationNamePrefix; - if ([input isOptionHold]) { + if ([input isOptionHold]) + { punctuationNamePrefix = std::string("_alt_punctuation_"); - } else if ([input isControlHold]) { + } + else if ([input isControlHold]) + { punctuationNamePrefix = std::string("_ctrl_punctuation_"); - } else if (mgrPrefs.halfWidthPunctuationEnabled) { + } + else if (mgrPrefs.halfWidthPunctuationEnabled) + { punctuationNamePrefix = std::string("_half_punctuation_"); - } else { + } + else + { punctuationNamePrefix = std::string("_punctuation_"); } std::string layout = [self _currentLayout]; - std::string customPunctuation = punctuationNamePrefix + layout + std::string(1, (char) charCode); - if ([self _handlePunctuation:customPunctuation state:state usingVerticalMode:input.useVerticalMode stateCallback:stateCallback errorCallback:errorCallback]) { + std::string customPunctuation = punctuationNamePrefix + layout + std::string(1, (char)charCode); + if ([self _handlePunctuation:customPunctuation + state:state + usingVerticalMode:input.useVerticalMode + stateCallback:stateCallback + errorCallback:errorCallback]) + { return YES; } // if nothing is matched, see if it's a punctuation key. - std::string punctuation = punctuationNamePrefix + std::string(1, (char) charCode); - if ([self _handlePunctuation:punctuation state:state usingVerticalMode:input.useVerticalMode stateCallback:stateCallback errorCallback:errorCallback]) { + std::string punctuation = punctuationNamePrefix + std::string(1, (char)charCode); + if ([self _handlePunctuation:punctuation + state:state + usingVerticalMode:input.useVerticalMode + stateCallback:stateCallback + errorCallback:errorCallback]) + { return YES; } // Lukhnos 這裡的處理反而會使得 Apple 倚天注音動態鍵盤佈局「敲不了半形大寫英文」的缺點曝露無疑,所以注釋掉。 - // 至於他試圖用這種處理來解決的上游 UPR293 的問題,其實針對詞庫檔案的排序做點手腳就可以解決。威注音本來也就是這麼做的。 - if (/*[state isKindOfClass:[InputStateNotEmpty class]] && */[input isUpperCaseASCIILetterKey]) { - std::string letter = std::string("_letter_") + std::string(1, (char) charCode); - if ([self _handlePunctuation:letter state:state usingVerticalMode:input.useVerticalMode stateCallback:stateCallback errorCallback:errorCallback]) { + // 至於他試圖用這種處理來解決的上游 UPR293 + // 的問題,其實針對詞庫檔案的排序做點手腳就可以解決。威注音本來也就是這麼做的。 + if (/*[state isKindOfClass:[InputStateNotEmpty class]] && */ [input isUpperCaseASCIILetterKey]) + { + std::string letter = std::string("_letter_") + std::string(1, (char)charCode); + if ([self _handlePunctuation:letter + state:state + usingVerticalMode:input.useVerticalMode + stateCallback:stateCallback + errorCallback:errorCallback]) + { return YES; } } - // still nothing, then we update the composing buffer (some app has strange behavior if we don't do this, "thinking" the key is not actually consumed) - // 砍掉這一段會導致「F1-F12 按鍵干擾組字區」的問題。暫時只能先恢復這段,且補上偵錯彙報機制,方便今後排查故障。 - if ([state isKindOfClass:[InputStateNotEmpty class]] || !_bpmfReadingBuffer->isEmpty()) { - [IME prtDebugIntel:[NSString stringWithFormat:@"Blocked data: charCode: %c, keyCode: %c", charCode, input.keyCode]]; + // still nothing, then we update the composing buffer (some app has strange behavior if we don't do this, "thinking" + // the key is not actually consumed) 砍掉這一段會導致「F1-F12 + // 按鍵干擾組字區」的問題。暫時只能先恢復這段,且補上偵錯彙報機制,方便今後排查故障。 + if ([state isKindOfClass:[InputStateNotEmpty class]] || !_bpmfReadingBuffer->isEmpty()) + { + [IME prtDebugIntel:[NSString + stringWithFormat:@"Blocked data: charCode: %c, keyCode: %c", charCode, input.keyCode]]; [IME prtDebugIntel:@"A9BFF20E"]; errorCallback(); stateCallback(state); @@ -604,33 +738,43 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; return NO; } -- (BOOL)_handleEscWithState:(InputState *)state stateCallback:(void (^)(InputState *))stateCallback errorCallback:(void (^)(void))errorCallback +- (BOOL)_handleEscWithState:(InputState *)state + stateCallback:(void (^)(InputState *))stateCallback + errorCallback:(void (^)(void))errorCallback { - if (![state isKindOfClass:[InputStateInputting class]]) { + if (![state isKindOfClass:[InputStateInputting class]]) + { return NO; } BOOL escToClearInputBufferEnabled = mgrPrefs.escToCleanInputBuffer; - if (escToClearInputBufferEnabled) { + if (escToClearInputBufferEnabled) + { // if the option is enabled, we clear everything including the composing // buffer, walked nodes and the reading. [self clear]; InputStateEmptyIgnoringPreviousState *empty = [[InputStateEmptyIgnoringPreviousState alloc] init]; stateCallback(empty); - } else { + } + else + { // if reading is not empty, we cancel the reading; Apple's built-in // Zhuyin (and the erstwhile Hanin) has a default option that Esc // "cancels" the current composed character and revert it to // Bopomofo reading, in odds with the expectation of users from // other platforms - if (!_bpmfReadingBuffer->isEmpty()) { + if (!_bpmfReadingBuffer->isEmpty()) + { _bpmfReadingBuffer->clear(); - if (!_builder->length()) { + if (!_builder->length()) + { InputStateEmpty *empty = [[InputStateEmpty alloc] init]; stateCallback(empty); - } else { + } + else + { InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; stateCallback(inputting); } @@ -639,39 +783,57 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; return YES; } -- (BOOL)_handleBackwardWithState:(InputState *)state input:(keyParser *)input stateCallback:(void (^)(InputState *))stateCallback errorCallback:(void (^)(void))errorCallback +- (BOOL)_handleBackwardWithState:(InputState *)state + input:(keyParser *)input + stateCallback:(void (^)(InputState *))stateCallback + errorCallback:(void (^)(void))errorCallback { - if (![state isKindOfClass:[InputStateInputting class]]) { + if (![state isKindOfClass:[InputStateInputting class]]) + { return NO; } - if (!_bpmfReadingBuffer->isEmpty()) { + if (!_bpmfReadingBuffer->isEmpty()) + { [IME prtDebugIntel:@"6ED95318"]; errorCallback(); stateCallback(state); return YES; } - InputStateInputting *currentState = (InputStateInputting *) state; + InputStateInputting *currentState = (InputStateInputting *)state; - if ([input isShiftHold]) { + if ([input isShiftHold]) + { // Shift + left - if (currentState.cursorIndex > 0) { - NSInteger previousPosition = [currentState.composingBuffer previousUtf16PositionFor:currentState.cursorIndex]; - InputStateMarking *marking = [[InputStateMarking alloc] initWithComposingBuffer:currentState.composingBuffer cursorIndex:currentState.cursorIndex markerIndex:previousPosition readings:[self _currentReadings]]; + if (currentState.cursorIndex > 0) + { + NSInteger previousPosition = + [currentState.composingBuffer previousUtf16PositionFor:currentState.cursorIndex]; + InputStateMarking *marking = [[InputStateMarking alloc] initWithComposingBuffer:currentState.composingBuffer + cursorIndex:currentState.cursorIndex + markerIndex:previousPosition + readings:[self _currentReadings]]; marking.tooltipForInputting = currentState.tooltip; stateCallback(marking); - } else { + } + else + { [IME prtDebugIntel:@"D326DEA3"]; errorCallback(); stateCallback(state); } - } else { - if (_builder->cursorIndex() > 0) { + } + else + { + if (_builder->cursorIndex() > 0) + { _builder->setCursorIndex(_builder->cursorIndex() - 1); InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; stateCallback(inputting); - } else { + } + else + { [IME prtDebugIntel:@"7045E6F3"]; errorCallback(); stateCallback(state); @@ -680,39 +842,56 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; return YES; } -- (BOOL)_handleForwardWithState:(InputState *)state input:(keyParser *)input stateCallback:(void (^)(InputState *))stateCallback errorCallback:(void (^)(void))errorCallback +- (BOOL)_handleForwardWithState:(InputState *)state + input:(keyParser *)input + stateCallback:(void (^)(InputState *))stateCallback + errorCallback:(void (^)(void))errorCallback { - if (![state isKindOfClass:[InputStateInputting class]]) { + if (![state isKindOfClass:[InputStateInputting class]]) + { return NO; } - if (!_bpmfReadingBuffer->isEmpty()) { + if (!_bpmfReadingBuffer->isEmpty()) + { [IME prtDebugIntel:@"B3BA5257"]; errorCallback(); stateCallback(state); return YES; } - InputStateInputting *currentState = (InputStateInputting *) state; + InputStateInputting *currentState = (InputStateInputting *)state; - if ([input isShiftHold]) { + if ([input isShiftHold]) + { // Shift + Right - if (currentState.cursorIndex < currentState.composingBuffer.length) { + if (currentState.cursorIndex < currentState.composingBuffer.length) + { NSInteger nextPosition = [currentState.composingBuffer nextUtf16PositionFor:currentState.cursorIndex]; - InputStateMarking *marking = [[InputStateMarking alloc] initWithComposingBuffer:currentState.composingBuffer cursorIndex:currentState.cursorIndex markerIndex:nextPosition readings:[self _currentReadings]]; + InputStateMarking *marking = [[InputStateMarking alloc] initWithComposingBuffer:currentState.composingBuffer + cursorIndex:currentState.cursorIndex + markerIndex:nextPosition + readings:[self _currentReadings]]; marking.tooltipForInputting = currentState.tooltip; stateCallback(marking); - } else { + } + else + { [IME prtDebugIntel:@"BB7F6DB9"]; errorCallback(); stateCallback(state); } - } else { - if (_builder->cursorIndex() < _builder->length()) { + } + else + { + if (_builder->cursorIndex() < _builder->length()) + { _builder->setCursorIndex(_builder->cursorIndex() + 1); InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; stateCallback(inputting); - } else { + } + else + { [IME prtDebugIntel:@"A96AAD58"]; errorCallback(); stateCallback(state); @@ -722,24 +901,31 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; return YES; } -- (BOOL)_handleHomeWithState:(InputState *)state stateCallback:(void (^)(InputState *))stateCallback errorCallback:(void (^)(void))errorCallback +- (BOOL)_handleHomeWithState:(InputState *)state + stateCallback:(void (^)(InputState *))stateCallback + errorCallback:(void (^)(void))errorCallback { - if (![state isKindOfClass:[InputStateInputting class]]) { + if (![state isKindOfClass:[InputStateInputting class]]) + { return NO; } - if (!_bpmfReadingBuffer->isEmpty()) { + if (!_bpmfReadingBuffer->isEmpty()) + { [IME prtDebugIntel:@"ABC44080"]; errorCallback(); stateCallback(state); return YES; } - if (_builder->cursorIndex()) { + if (_builder->cursorIndex()) + { _builder->setCursorIndex(0); InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; stateCallback(inputting); - } else { + } + else + { [IME prtDebugIntel:@"66D97F90"]; errorCallback(); stateCallback(state); @@ -748,24 +934,31 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; return YES; } -- (BOOL)_handleEndWithState:(InputState *)state stateCallback:(void (^)(InputState *))stateCallback errorCallback:(void (^)(void))errorCallback +- (BOOL)_handleEndWithState:(InputState *)state + stateCallback:(void (^)(InputState *))stateCallback + errorCallback:(void (^)(void))errorCallback { - if (![state isKindOfClass:[InputStateInputting class]]) { + if (![state isKindOfClass:[InputStateInputting class]]) + { return NO; } - if (!_bpmfReadingBuffer->isEmpty()) { + if (!_bpmfReadingBuffer->isEmpty()) + { [IME prtDebugIntel:@"9B69908D"]; errorCallback(); stateCallback(state); return YES; } - if (_builder->cursorIndex() != _builder->length()) { + if (_builder->cursorIndex() != _builder->length()) + { _builder->setCursorIndex(_builder->length()); InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; stateCallback(inputting); - } else { + } + else + { [IME prtDebugIntel:@"9B69908E"]; errorCallback(); stateCallback(state); @@ -774,13 +967,17 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; return YES; } -- (BOOL)_handleAbsorbedArrowKeyWithState:(InputState *)state stateCallback:(void (^)(InputState *))stateCallback errorCallback:(void (^)(void))errorCallback +- (BOOL)_handleAbsorbedArrowKeyWithState:(InputState *)state + stateCallback:(void (^)(InputState *))stateCallback + errorCallback:(void (^)(void))errorCallback { - if (![state isKindOfClass:[InputStateInputting class]]) { + if (![state isKindOfClass:[InputStateInputting class]]) + { return NO; } - if (!_bpmfReadingBuffer->isEmpty()) { + if (!_bpmfReadingBuffer->isEmpty()) + { [IME prtDebugIntel:@"9B6F908D"]; errorCallback(); } @@ -788,59 +985,83 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; return YES; } -- (BOOL)_handleBackspaceWithState:(InputState *)state stateCallback:(void (^)(InputState *))stateCallback errorCallback:(void (^)(void))errorCallback +- (BOOL)_handleBackspaceWithState:(InputState *)state + stateCallback:(void (^)(InputState *))stateCallback + errorCallback:(void (^)(void))errorCallback { - if (![state isKindOfClass:[InputStateInputting class]]) { + if (![state isKindOfClass:[InputStateInputting class]]) + { return NO; } - if (_bpmfReadingBuffer->isEmpty()) { - if (_builder->cursorIndex()) { + if (_bpmfReadingBuffer->isEmpty()) + { + if (_builder->cursorIndex()) + { _builder->deleteReadingBeforeCursor(); [self _walk]; - } else { + } + else + { [IME prtDebugIntel:@"9D69908D"]; errorCallback(); stateCallback(state); return YES; } - } else { + } + else + { _bpmfReadingBuffer->backspace(); } - if (_bpmfReadingBuffer->isEmpty() && !_builder->length()) { + if (_bpmfReadingBuffer->isEmpty() && !_builder->length()) + { InputStateEmptyIgnoringPreviousState *empty = [[InputStateEmptyIgnoringPreviousState alloc] init]; stateCallback(empty); - } else { + } + else + { InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; stateCallback(inputting); } return YES; } -- (BOOL)_handleDeleteWithState:(InputState *)state stateCallback:(void (^)(InputState *))stateCallback errorCallback:(void (^)(void))errorCallback +- (BOOL)_handleDeleteWithState:(InputState *)state + stateCallback:(void (^)(InputState *))stateCallback + errorCallback:(void (^)(void))errorCallback { - if (![state isKindOfClass:[InputStateInputting class]]) { + if (![state isKindOfClass:[InputStateInputting class]]) + { return NO; } - if (_bpmfReadingBuffer->isEmpty()) { - if (_builder->cursorIndex() != _builder->length()) { + if (_bpmfReadingBuffer->isEmpty()) + { + if (_builder->cursorIndex() != _builder->length()) + { _builder->deleteReadingAfterCursor(); [self _walk]; InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; - if (!inputting.composingBuffer.length) { + if (!inputting.composingBuffer.length) + { InputStateEmptyIgnoringPreviousState *empty = [[InputStateEmptyIgnoringPreviousState alloc] init]; stateCallback(empty); - } else { + } + else + { stateCallback(inputting); } - } else { + } + else + { [IME prtDebugIntel:@"9B69938D"]; errorCallback(); stateCallback(state); } - } else { + } + else + { [IME prtDebugIntel:@"9C69908D"]; errorCallback(); stateCallback(state); @@ -849,12 +1070,17 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; return YES; } -- (BOOL)_handleCtrlCommandEnterWithState:(InputState *)state stateCallback:(void (^)(InputState *))stateCallback errorCallback:(void (^)(void))errorCallback +- (BOOL)_handleCtrlCommandEnterWithState:(InputState *)state + stateCallback:(void (^)(InputState *))stateCallback + errorCallback:(void (^)(void))errorCallback { - if (![state isKindOfClass:[InputStateInputting class]]) return NO; + if (![state isKindOfClass:[InputStateInputting class]]) + return NO; NSArray *readings = [self _currentReadings]; - NSString *composingBuffer = (ctlInputMethod.areWeUsingOurOwnPhraseEditor) ? [readings componentsJoinedByString:@"-"] : [readings componentsJoinedByString:@" "] ; + NSString *composingBuffer = (ctlInputMethod.areWeUsingOurOwnPhraseEditor) + ? [readings componentsJoinedByString:@"-"] + : [readings componentsJoinedByString:@" "]; [self clear]; @@ -865,15 +1091,18 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; return YES; } -- (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]]) + { return NO; } [self clear]; - InputStateInputting *current = (InputStateInputting *) state; + InputStateInputting *current = (InputStateInputting *)state; NSString *composingBuffer = current.composingBuffer; InputStateCommitting *committing = [[InputStateCommitting alloc] initWithPoppedText:composingBuffer]; stateCallback(committing); @@ -882,17 +1111,25 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; return YES; } -- (BOOL)_handlePunctuation:(std::string)customPunctuation state:(InputState *)state usingVerticalMode:(BOOL)useVerticalMode stateCallback:(void (^)(InputState *))stateCallback errorCallback:(void (^)(void))errorCallback +- (BOOL)_handlePunctuation:(std::string)customPunctuation + state:(InputState *)state + usingVerticalMode:(BOOL)useVerticalMode + stateCallback:(void (^)(InputState *))stateCallback + errorCallback:(void (^)(void))errorCallback { - if (!_languageModel->hasUnigramsForKey(customPunctuation)) { + if (!_languageModel->hasUnigramsForKey(customPunctuation)) + { return NO; } NSString *poppedText; - if (_bpmfReadingBuffer->isEmpty()) { + if (_bpmfReadingBuffer->isEmpty()) + { _builder->insertReadingAtCursor(customPunctuation); poppedText = [self _popOverflowComposingTextAndWalk]; - } else { // If there is still unfinished bpmf reading, ignore the punctuation + } + else + { // If there is still unfinished bpmf reading, ignore the punctuation [IME prtDebugIntel:@"A9B69908D"]; errorCallback(); stateCallback(state); @@ -903,38 +1140,46 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; inputting.poppedText = poppedText; stateCallback(inputting); - if (mgrPrefs.useSCPCTypingMode && _bpmfReadingBuffer->isEmpty()) { - InputStateChoosingCandidate *candidateState = [self _buildCandidateState:inputting useVerticalMode:useVerticalMode]; + if (mgrPrefs.useSCPCTypingMode && _bpmfReadingBuffer->isEmpty()) + { + InputStateChoosingCandidate *candidateState = [self _buildCandidateState:inputting + useVerticalMode:useVerticalMode]; - if ([candidateState.candidates count] == 1) { + if ([candidateState.candidates count] == 1) + { [self clear]; - InputStateCommitting *committing = [[InputStateCommitting alloc] initWithPoppedText:candidateState.candidates.firstObject]; + InputStateCommitting *committing = + [[InputStateCommitting alloc] initWithPoppedText:candidateState.candidates.firstObject]; stateCallback(committing); InputStateEmpty *empty = [[InputStateEmpty alloc] init]; stateCallback(empty); - } else { + } + else + { stateCallback(candidateState); } } return YES; } - - (BOOL)_handleMarkingState:(InputStateMarking *)state input:(keyParser *)input stateCallback:(void (^)(InputState *))stateCallback errorCallback:(void (^)(void))errorCallback { - if ([input isESC]) { + if ([input isESC]) + { InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; stateCallback(inputting); return YES; } // Enter - if ([input isEnter]) { - if (![self.delegate keyHandler:self didRequestWriteUserPhraseWithState:state]) { + if ([input isEnter]) + { + if (![self.delegate keyHandler:self didRequestWriteUserPhraseWithState:state]) + { [IME prtDebugIntel:@"5B69CC8D"]; errorCallback(); return YES; @@ -945,21 +1190,30 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; } // Shift + left - if (([input isCursorBackward] || input.emacsKey == vChewingEmacsKeyBackward) - && ([input isShiftHold])) { + if (([input isCursorBackward] || input.emacsKey == vChewingEmacsKeyBackward) && ([input isShiftHold])) + { NSUInteger index = state.markerIndex; - if (index > 0) { + if (index > 0) + { index = [state.composingBuffer previousUtf16PositionFor:index]; - InputStateMarking *marking = [[InputStateMarking alloc] initWithComposingBuffer:state.composingBuffer cursorIndex:state.cursorIndex markerIndex:index readings:state.readings]; + InputStateMarking *marking = [[InputStateMarking alloc] initWithComposingBuffer:state.composingBuffer + cursorIndex:state.cursorIndex + markerIndex:index + readings:state.readings]; marking.tooltipForInputting = state.tooltipForInputting; - if (marking.markedRange.length == 0) { + if (marking.markedRange.length == 0) + { InputState *inputting = [marking convertToInputting]; stateCallback(inputting); - } else { + } + else + { stateCallback(marking); } - } else { + } + else + { [IME prtDebugIntel:@"1149908D"]; errorCallback(); stateCallback(state); @@ -968,20 +1222,29 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; } // Shift + Right - if (([input isCursorForward] || input.emacsKey == vChewingEmacsKeyForward) - && ([input isShiftHold])) { + if (([input isCursorForward] || input.emacsKey == vChewingEmacsKeyForward) && ([input isShiftHold])) + { NSUInteger index = state.markerIndex; - if (index < state.composingBuffer.length) { + if (index < state.composingBuffer.length) + { index = [state.composingBuffer nextUtf16PositionFor:index]; - InputStateMarking *marking = [[InputStateMarking alloc] initWithComposingBuffer:state.composingBuffer cursorIndex:state.cursorIndex markerIndex:index readings:state.readings]; + InputStateMarking *marking = [[InputStateMarking alloc] initWithComposingBuffer:state.composingBuffer + cursorIndex:state.cursorIndex + markerIndex:index + readings:state.readings]; marking.tooltipForInputting = state.tooltipForInputting; - if (marking.markedRange.length == 0) { + if (marking.markedRange.length == 0) + { InputState *inputting = [marking convertToInputting]; stateCallback(inputting); - } else { + } + else + { stateCallback(marking); } - } else { + } + else + { [IME prtDebugIntel:@"9B51408D"]; errorCallback(); stateCallback(state); @@ -991,7 +1254,6 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; return NO; } - - (BOOL)_handleCandidateState:(InputState *)state input:(keyParser *)input stateCallback:(void (^)(InputState *))stateCallback @@ -1001,99 +1263,122 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; UniChar charCode = input.charCode; VTCandidateController *gCurrentCandidateController = [self.delegate candidateControllerForKeyHandler:self]; - BOOL cancelCandidateKey = [input isBackSpace] || [input isESC] || [input isDelete] - || (([input isCursorBackward] || [input isCursorForward]) && [input isShiftHold]); + BOOL cancelCandidateKey = [input isBackSpace] || [input isESC] || [input isDelete] || + (([input isCursorBackward] || [input isCursorForward]) && [input isShiftHold]); - if (cancelCandidateKey) { - if ([state isKindOfClass: [InputStateAssociatedPhrases class]]) { + if (cancelCandidateKey) + { + if ([state isKindOfClass:[InputStateAssociatedPhrases class]]) + { [self clear]; InputStateEmptyIgnoringPreviousState *empty = [[InputStateEmptyIgnoringPreviousState alloc] init]; stateCallback(empty); } - else if (mgrPrefs.useSCPCTypingMode) { + else if (mgrPrefs.useSCPCTypingMode) + { [self clear]; InputStateEmptyIgnoringPreviousState *empty = [[InputStateEmptyIgnoringPreviousState alloc] init]; stateCallback(empty); - } else if ([self isBuilderEmpty]) { + } + else if ([self isBuilderEmpty]) + { // 如果此時發現當前組字緩衝區為真空的情況的話,就將當前的組字緩衝區析構處理、強制重設輸入狀態。 // 不然的話,一個本不該出現的真空組字緩衝區會使前後方向鍵與 BackSpace 鍵失靈。 [self clear]; InputStateEmptyIgnoringPreviousState *empty = [[InputStateEmptyIgnoringPreviousState alloc] init]; stateCallback(empty); - } else { + } + else + { InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; stateCallback(inputting); } return YES; } - if ([input isEnter]) { - if ([state isKindOfClass: [InputStateAssociatedPhrases class]]) { + if ([input isEnter]) + { + if ([state isKindOfClass:[InputStateAssociatedPhrases class]]) + { [self clear]; InputStateEmptyIgnoringPreviousState *empty = [[InputStateEmptyIgnoringPreviousState alloc] init]; stateCallback(empty); return YES; } - [self.delegate keyHandler:self didSelectCandidateAtIndex:gCurrentCandidateController.selectedCandidateIndex candidateController:gCurrentCandidateController]; + [self.delegate keyHandler:self + didSelectCandidateAtIndex:gCurrentCandidateController.selectedCandidateIndex + candidateController:gCurrentCandidateController]; return YES; } - if ([input isTab]) { - BOOL updated = - mgrPrefs.specifyTabKeyBehavior? - ([input isShiftHold] ? [gCurrentCandidateController showPreviousPage] : [gCurrentCandidateController showNextPage]) - : - ([input isShiftHold] ? [gCurrentCandidateController highlightPreviousCandidate] : [gCurrentCandidateController highlightNextCandidate]) - ; - if (!updated) { + if ([input isTab]) + { + BOOL updated = mgrPrefs.specifyTabKeyBehavior + ? ([input isShiftHold] ? [gCurrentCandidateController showPreviousPage] + : [gCurrentCandidateController showNextPage]) + : ([input isShiftHold] ? [gCurrentCandidateController highlightPreviousCandidate] + : [gCurrentCandidateController highlightNextCandidate]); + if (!updated) + { [IME prtDebugIntel:@"9B691919"]; errorCallback(); } return YES; } - if ([input isSpace]) { - BOOL updated = - mgrPrefs.specifySpaceKeyBehavior? - ([input isShiftHold] ? [gCurrentCandidateController highlightNextCandidate] : [gCurrentCandidateController showNextPage]) - : - ([input isShiftHold] ? [gCurrentCandidateController showNextPage] : [gCurrentCandidateController highlightNextCandidate]) - ; - if (!updated) { + if ([input isSpace]) + { + BOOL updated = mgrPrefs.specifySpaceKeyBehavior + ? ([input isShiftHold] ? [gCurrentCandidateController highlightNextCandidate] + : [gCurrentCandidateController showNextPage]) + : ([input isShiftHold] ? [gCurrentCandidateController showNextPage] + : [gCurrentCandidateController highlightNextCandidate]); + if (!updated) + { [IME prtDebugIntel:@"A11C781F"]; errorCallback(); } return YES; } - if ([input isPageDown] || input.emacsKey == vChewingEmacsKeyNextPage) { + if ([input isPageDown] || input.emacsKey == vChewingEmacsKeyNextPage) + { BOOL updated = [gCurrentCandidateController showNextPage]; - if (!updated) { + if (!updated) + { [IME prtDebugIntel:@"9B691919"]; errorCallback(); } return YES; } - if ([input isPageUp]) { + if ([input isPageUp]) + { BOOL updated = [gCurrentCandidateController showPreviousPage]; - if (!updated) { + if (!updated) + { [IME prtDebugIntel:@"9569955D"]; errorCallback(); } return YES; } - if ([input isLeft]) { - if ([gCurrentCandidateController isKindOfClass:[VTHorizontalCandidateController class]]) { + if ([input isLeft]) + { + if ([gCurrentCandidateController isKindOfClass:[VTHorizontalCandidateController class]]) + { BOOL updated = [gCurrentCandidateController highlightPreviousCandidate]; - if (!updated) { + if (!updated) + { [IME prtDebugIntel:@"1145148D"]; errorCallback(); } - } else { + } + else + { BOOL updated = [gCurrentCandidateController showPreviousPage]; - if (!updated) { + if (!updated) + { [IME prtDebugIntel:@"1919810D"]; errorCallback(); } @@ -1101,25 +1386,33 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; return YES; } - if (input.emacsKey == vChewingEmacsKeyBackward) { + if (input.emacsKey == vChewingEmacsKeyBackward) + { BOOL updated = [gCurrentCandidateController highlightPreviousCandidate]; - if (!updated) { + if (!updated) + { [IME prtDebugIntel:@"9B89308D"]; errorCallback(); } return YES; } - if ([input isRight]) { - if ([gCurrentCandidateController isKindOfClass:[VTHorizontalCandidateController class]]) { + if ([input isRight]) + { + if ([gCurrentCandidateController isKindOfClass:[VTHorizontalCandidateController class]]) + { BOOL updated = [gCurrentCandidateController highlightNextCandidate]; - if (!updated) { + if (!updated) + { [IME prtDebugIntel:@"9B65138D"]; errorCallback(); } - } else { + } + else + { BOOL updated = [gCurrentCandidateController showNextPage]; - if (!updated) { + if (!updated) + { [IME prtDebugIntel:@"9244908D"]; errorCallback(); } @@ -1127,25 +1420,33 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; return YES; } - if (input.emacsKey == vChewingEmacsKeyForward) { + if (input.emacsKey == vChewingEmacsKeyForward) + { BOOL updated = [gCurrentCandidateController highlightNextCandidate]; - if (!updated) { + if (!updated) + { [IME prtDebugIntel:@"9B2428D"]; errorCallback(); } return YES; } - if ([input isUp]) { - if ([gCurrentCandidateController isKindOfClass:[VTHorizontalCandidateController class]]) { + if ([input isUp]) + { + if ([gCurrentCandidateController isKindOfClass:[VTHorizontalCandidateController class]]) + { BOOL updated = [gCurrentCandidateController showPreviousPage]; - if (!updated) { + if (!updated) + { [IME prtDebugIntel:@"9B614524"]; errorCallback(); } - } else { + } + else + { BOOL updated = [gCurrentCandidateController highlightPreviousCandidate]; - if (!updated) { + if (!updated) + { [IME prtDebugIntel:@"ASD9908D"]; errorCallback(); } @@ -1153,16 +1454,22 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; return YES; } - if ([input isDown]) { - if ([gCurrentCandidateController isKindOfClass:[VTHorizontalCandidateController class]]) { + if ([input isDown]) + { + if ([gCurrentCandidateController isKindOfClass:[VTHorizontalCandidateController class]]) + { BOOL updated = [gCurrentCandidateController showNextPage]; - if (!updated) { + if (!updated) + { [IME prtDebugIntel:@"92B990DD"]; errorCallback(); } - } else { + } + else + { BOOL updated = [gCurrentCandidateController highlightNextCandidate]; - if (!updated) { + if (!updated) + { [IME prtDebugIntel:@"6B99908D"]; errorCallback(); } @@ -1170,11 +1477,15 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; return YES; } - if ([input isHome] || input.emacsKey == vChewingEmacsKeyHome) { - if (gCurrentCandidateController.selectedCandidateIndex == 0) { + if ([input isHome] || input.emacsKey == vChewingEmacsKeyHome) + { + if (gCurrentCandidateController.selectedCandidateIndex == 0) + { [IME prtDebugIntel:@"9B6EDE8D"]; errorCallback(); - } else { + } + else + { gCurrentCandidateController.selectedCandidateIndex = 0; } @@ -1183,89 +1494,124 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; NSArray *candidates; - if ([state isKindOfClass: [InputStateChoosingCandidate class]]) { + if ([state isKindOfClass:[InputStateChoosingCandidate class]]) + { candidates = [(InputStateChoosingCandidate *)state candidates]; - } else if ([state isKindOfClass: [InputStateAssociatedPhrases class]]) { + } + else if ([state isKindOfClass:[InputStateAssociatedPhrases class]]) + { candidates = [(InputStateAssociatedPhrases *)state candidates]; } - if (!candidates) { + if (!candidates) + { return NO; } - if (([input isEnd] || input.emacsKey == vChewingEmacsKeyEnd) && candidates.count > 0) { - if (gCurrentCandidateController.selectedCandidateIndex == candidates.count - 1) { + if (([input isEnd] || input.emacsKey == vChewingEmacsKeyEnd) && candidates.count > 0) + { + if (gCurrentCandidateController.selectedCandidateIndex == candidates.count - 1) + { [IME prtDebugIntel:@"9B69AAAD"]; errorCallback(); - } else { + } + else + { gCurrentCandidateController.selectedCandidateIndex = candidates.count - 1; } return YES; } - if ([state isKindOfClass:[InputStateAssociatedPhrases class]]) { - if (![input isShiftHold]) { + if ([state isKindOfClass:[InputStateAssociatedPhrases class]]) + { + if (![input isShiftHold]) + { return NO; } } NSInteger index = NSNotFound; NSString *match; - if ([state isKindOfClass:[InputStateAssociatedPhrases class]]) { + if ([state isKindOfClass:[InputStateAssociatedPhrases class]]) + { match = input.inputTextIgnoringModifiers; - } else { + } + else + { match = inputText; } - for (NSUInteger j = 0, c = [gCurrentCandidateController.keyLabels count]; j < c; j++) { + for (NSUInteger j = 0, c = [gCurrentCandidateController.keyLabels count]; j < c; j++) + { VTCandidateKeyLabel *label = gCurrentCandidateController.keyLabels[j]; - if ([match compare:label.key options:NSCaseInsensitiveSearch] == NSOrderedSame) { + if ([match compare:label.key options:NSCaseInsensitiveSearch] == NSOrderedSame) + { index = j; break; } } - if (index != NSNotFound) { + if (index != NSNotFound) + { NSUInteger candidateIndex = [gCurrentCandidateController candidateIndexAtKeyLabelIndex:index]; - if (candidateIndex != NSUIntegerMax) { - [self.delegate keyHandler:self didSelectCandidateAtIndex:candidateIndex candidateController:gCurrentCandidateController]; + if (candidateIndex != NSUIntegerMax) + { + [self.delegate keyHandler:self + didSelectCandidateAtIndex:candidateIndex + candidateController:gCurrentCandidateController]; return YES; } } - if ([state isKindOfClass:[InputStateAssociatedPhrases class]]) { + if ([state isKindOfClass:[InputStateAssociatedPhrases class]]) + { return NO; } - if (mgrPrefs.useSCPCTypingMode) { + if (mgrPrefs.useSCPCTypingMode) + { std::string layout = [self _currentLayout]; std::string punctuationNamePrefix; - if ([input isOptionHold]) { + if ([input isOptionHold]) + { punctuationNamePrefix = std::string("_alt_punctuation_"); - } else if ([input isControlHold]) { + } + else if ([input isControlHold]) + { punctuationNamePrefix = std::string("_ctrl_punctuation_"); - } else if (mgrPrefs.halfWidthPunctuationEnabled) { + } + else if (mgrPrefs.halfWidthPunctuationEnabled) + { punctuationNamePrefix = std::string("_half_punctuation_"); - } else { + } + else + { punctuationNamePrefix = std::string("_punctuation_"); } - std::string customPunctuation = punctuationNamePrefix + layout + std::string(1, (char) charCode); - std::string punctuation = punctuationNamePrefix + std::string(1, (char) charCode); + std::string customPunctuation = punctuationNamePrefix + layout + std::string(1, (char)charCode); + std::string punctuation = punctuationNamePrefix + std::string(1, (char)charCode); - BOOL shouldAutoSelectCandidate = _bpmfReadingBuffer->isValidKey((char) charCode) || _languageModel->hasUnigramsForKey(customPunctuation) || - _languageModel->hasUnigramsForKey(punctuation); + BOOL shouldAutoSelectCandidate = _bpmfReadingBuffer->isValidKey((char)charCode) || + _languageModel->hasUnigramsForKey(customPunctuation) || + _languageModel->hasUnigramsForKey(punctuation); - if (!shouldAutoSelectCandidate && [input isUpperCaseASCIILetterKey]) { - std::string letter = std::string("_letter_") + std::string(1, (char) charCode); - if (_languageModel->hasUnigramsForKey(letter)) { + if (!shouldAutoSelectCandidate && [input isUpperCaseASCIILetterKey]) + { + std::string letter = std::string("_letter_") + std::string(1, (char)charCode); + if (_languageModel->hasUnigramsForKey(letter)) + { shouldAutoSelectCandidate = YES; } } - if (shouldAutoSelectCandidate) { + if (shouldAutoSelectCandidate) + { NSUInteger candidateIndex = [gCurrentCandidateController candidateIndexAtKeyLabelIndex:0]; - if (candidateIndex != NSUIntegerMax) { - [self.delegate keyHandler:self didSelectCandidateAtIndex:candidateIndex candidateController:gCurrentCandidateController]; + if (candidateIndex != NSUIntegerMax) + { + [self.delegate keyHandler:self + didSelectCandidateAtIndex:candidateIndex + candidateController:gCurrentCandidateController]; [self clear]; InputStateEmptyIgnoringPreviousState *empty = [[InputStateEmptyIgnoringPreviousState alloc] init]; stateCallback(empty); @@ -1297,8 +1643,11 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; // we must do some Unicode codepoint counting to find the actual cursor location for the client // i.e. we need to take UTF-16 into consideration, for which a surrogate pair takes 2 UniChars // locations - for (std::vector::iterator wi = _walkedNodes.begin(), we = _walkedNodes.end(); wi != we; ++wi) { - if ((*wi).node) { + for (std::vector::iterator wi = _walkedNodes.begin(), we = _walkedNodes.end(); wi != we; + ++wi) + { + if ((*wi).node) + { std::string nodeStr = (*wi).node->currentKeyValue().value; NSString *valueString = [NSString stringWithUTF8String:nodeStr.c_str()]; [composingBuffer appendString:valueString]; @@ -1313,32 +1662,57 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; // accumulate those lengths to calculate the displayed cursor // index size_t spanningLength = (*wi).spanningLength; - if (readingCursorIndex + spanningLength <= builderCursorIndex) { + if (readingCursorIndex + spanningLength <= builderCursorIndex) + { composedStringCursorIndex += [valueString length]; readingCursorIndex += spanningLength; - } else { - if (codepointCount == spanningLength) { - for (size_t i = 0; i < codepointCount && readingCursorIndex < builderCursorIndex; i++) { + } + else + { + if (codepointCount == spanningLength) + { + for (size_t i = 0; i < codepointCount && readingCursorIndex < builderCursorIndex; i++) + { composedStringCursorIndex += [splited[i] length]; readingCursorIndex++; } - } else { - if (readingCursorIndex < builderCursorIndex) { + } + else + { + if (readingCursorIndex < builderCursorIndex) + { composedStringCursorIndex += [valueString length]; readingCursorIndex += spanningLength; - if (readingCursorIndex > builderCursorIndex) { + if (readingCursorIndex > builderCursorIndex) + { readingCursorIndex = builderCursorIndex; } - if (builderCursorIndex == 0) { - tooltip = [NSString stringWithFormat:NSLocalizedString(@"Cursor is before \"%@\".", @""), - [NSString stringWithUTF8String:_builder->readings()[builderCursorIndex].c_str()]]; - } else if (builderCursorIndex >= _builder->readings().size()) { - tooltip = [NSString stringWithFormat:NSLocalizedString(@"Cursor is after \"%@\".", @""), - [NSString stringWithUTF8String:_builder->readings()[_builder->readings().size() - 1].c_str()]]; - } else { - tooltip = [NSString stringWithFormat:NSLocalizedString(@"Cursor is between \"%@\" and \"%@\".", @""), - [NSString stringWithUTF8String:_builder->readings()[builderCursorIndex - 1].c_str()], - [NSString stringWithUTF8String:_builder->readings()[builderCursorIndex].c_str()]]; + if (builderCursorIndex == 0) + { + tooltip = [NSString + stringWithFormat:NSLocalizedString(@"Cursor is before \"%@\".", @""), + [NSString stringWithUTF8String:_builder->readings()[builderCursorIndex] + .c_str()]]; + } + else if (builderCursorIndex >= _builder->readings().size()) + { + tooltip = [NSString + stringWithFormat:NSLocalizedString(@"Cursor is after \"%@\".", @""), + [NSString + stringWithUTF8String:_builder + ->readings()[_builder->readings().size() - + 1] + .c_str()]]; + } + else + { + tooltip = [NSString + stringWithFormat:NSLocalizedString(@"Cursor is between \"%@\" and \"%@\".", @""), + [NSString + stringWithUTF8String:_builder->readings()[builderCursorIndex - 1] + .c_str()], + [NSString stringWithUTF8String:_builder->readings()[builderCursorIndex] + .c_str()]]; } } } @@ -1355,7 +1729,8 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; NSString *composedText = [head stringByAppendingString:[reading stringByAppendingString:tail]]; NSInteger cursorIndex = composedStringCursorIndex + [reading length]; - InputStateInputting *newState = [[InputStateInputting alloc] initWithComposingBuffer:composedText cursorIndex:cursorIndex]; + InputStateInputting *newState = [[InputStateInputting alloc] initWithComposingBuffer:composedText + cursorIndex:cursorIndex]; newState.tooltip = tooltip; return newState; } @@ -1379,7 +1754,10 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; NSString *dotStr = [NSString stringWithUTF8String:dotDump.c_str()]; NSError *error = nil; - BOOL __unused success = [dotStr writeToFile:kGraphVizOutputfile atomically:YES encoding:NSUTF8StringEncoding error:&error]; + BOOL __unused success = [dotStr writeToFile:kGraphVizOutputfile + atomically:YES + encoding:NSUTF8StringEncoding + error:&error]; #endif } @@ -1396,8 +1774,10 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; NSString *poppedText = @""; NSInteger composingBufferSize = mgrPrefs.composingBufferSize; - if (_builder->grid().width() > (size_t) composingBufferSize) { - if (_walkedNodes.size() > 0) { + if (_builder->grid().width() > (size_t)composingBufferSize) + { + if (_walkedNodes.size() > 0) + { Gramambular::NodeAnchor &anchor = _walkedNodes[0]; poppedText = [NSString stringWithUTF8String:anchor.node->currentKeyValue().value.c_str()]; _builder->removeHeadReadings(anchor.spanningLength); @@ -1408,7 +1788,8 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; return poppedText; } -- (InputStateChoosingCandidate *)_buildCandidateState:(InputStateNotEmpty *)currentState useVerticalMode:(BOOL)useVerticalMode +- (InputStateChoosingCandidate *)_buildCandidateState:(InputStateNotEmpty *)currentState + useVerticalMode:(BOOL)useVerticalMode { NSMutableArray *candidatesArray = [[NSMutableArray alloc] init]; @@ -1419,14 +1800,21 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; stable_sort(nodes.begin(), nodes.end(), NodeAnchorDescendingSorter()); // then use the C++ trick to retrieve the candidates for each node at/crossing the cursor - for (std::vector::iterator ni = nodes.begin(), ne = nodes.end(); ni != ne; ++ni) { + for (std::vector::iterator ni = nodes.begin(), ne = nodes.end(); ni != ne; ++ni) + { const std::vector &candidates = (*ni).node->candidates(); - for (std::vector::const_iterator ci = candidates.begin(), ce = candidates.end(); ci != ce; ++ci) { + for (std::vector::const_iterator ci = candidates.begin(), ce = candidates.end(); + ci != ce; ++ci) + { [candidatesArray addObject:[NSString stringWithUTF8String:(*ci).value.c_str()]]; } } - InputStateChoosingCandidate *state = [[InputStateChoosingCandidate alloc] initWithComposingBuffer:currentState.composingBuffer cursorIndex:currentState.cursorIndex candidates:candidatesArray useVerticalMode:useVerticalMode]; + InputStateChoosingCandidate *state = + [[InputStateChoosingCandidate alloc] initWithComposingBuffer:currentState.composingBuffer + cursorIndex:currentState.cursorIndex + candidates:candidatesArray + useVerticalMode:useVerticalMode]; return state; } @@ -1434,8 +1822,8 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; { size_t cursorIndex = _builder->cursorIndex(); // MS Phonetics IME style, phrase is *after* the cursor, i.e. cursor is always *before* the phrase - if ((mgrPrefs.selectPhraseAfterCursorAsCandidate && (cursorIndex < _builder->length())) - || !cursorIndex) { + if ((mgrPrefs.selectPhraseAfterCursorAsCandidate && (cursorIndex < _builder->length())) || !cursorIndex) + { ++cursorIndex; } @@ -1446,7 +1834,8 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; { NSMutableArray *readingsArray = [[NSMutableArray alloc] init]; std::vector v = _builder->readings(); - for (std::vector::iterator it_i = v.begin(); it_i != v.end(); ++it_i) { + for (std::vector::iterator it_i = v.begin(); it_i != v.end(); ++it_i) + { [readingsArray addObject:[NSString stringWithUTF8String:it_i->c_str()]]; } return readingsArray; @@ -1455,14 +1844,17 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; - (nullable InputState *)buildAssociatePhraseStateWithKey:(NSString *)key useVerticalMode:(BOOL)useVerticalMode { std::string cppKey = std::string(key.UTF8String); - if (_languageModel->hasAssociatedPhrasesForKey(cppKey)) { + if (_languageModel->hasAssociatedPhrasesForKey(cppKey)) + { std::vector phrases = _languageModel->associatedPhrasesForKey(cppKey); - NSMutableArray *array = [NSMutableArray array]; - for (auto phrase: phrases) { + NSMutableArray *array = [NSMutableArray array]; + for (auto phrase : phrases) + { NSString *item = [[NSString alloc] initWithUTF8String:phrase.c_str()]; [array addObject:item]; } - InputStateAssociatedPhrases *associatedPhrases = [[InputStateAssociatedPhrases alloc] initWithCandidates:array useVerticalMode:useVerticalMode]; + InputStateAssociatedPhrases *associatedPhrases = + [[InputStateAssociatedPhrases alloc] initWithCandidates:array useVerticalMode:useVerticalMode]; return associatedPhrases; } return nil; diff --git a/Source/Modules/ControllerModules/KeyValueBlobReader.cpp b/Source/Modules/ControllerModules/KeyValueBlobReader.cpp index 68d4f13b..eee32bbf 100644 --- a/Source/Modules/ControllerModules/KeyValueBlobReader.cpp +++ b/Source/Modules/ControllerModules/KeyValueBlobReader.cpp @@ -1,55 +1,67 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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. */ #include "KeyValueBlobReader.h" -namespace vChewing { +namespace vChewing +{ -KeyValueBlobReader::State KeyValueBlobReader::Next(KeyValue* out) +KeyValueBlobReader::State KeyValueBlobReader::Next(KeyValue *out) { static auto new_line = [](char c) { return c == '\n' || c == '\r'; }; static auto blank = [](char c) { return c == ' ' || c == '\t'; }; - static auto blank_or_newline - = [](char c) { return blank(c) || new_line(c); }; + static auto blank_or_newline = [](char c) { return blank(c) || new_line(c); }; static auto content_char = [](char c) { return !blank(c) && !new_line(c); }; - if (state_ == State::ERROR) { + if (state_ == State::ERROR) + { return state_; } - const char* key_begin = nullptr; + const char *key_begin = nullptr; size_t key_length = 0; - const char* value_begin = nullptr; + const char *value_begin = nullptr; size_t value_length = 0; - while (true) { + while (true) + { state_ = SkipUntilNot(blank_or_newline); - if (state_ != State::CAN_CONTINUE) { + if (state_ != State::CAN_CONTINUE) + { return state_; } // Check if it's a comment line; if so, read until end of line. - if (*current_ != '#') { + if (*current_ != '#') + { break; } state_ = SkipUntil(new_line); - if (state_ != State::CAN_CONTINUE) { + if (state_ != State::CAN_CONTINUE) + { return state_; } } @@ -59,22 +71,26 @@ KeyValueBlobReader::State KeyValueBlobReader::Next(KeyValue* out) key_begin = current_; state_ = SkipUntilNot(content_char); - if (state_ != State::CAN_CONTINUE) { + if (state_ != State::CAN_CONTINUE) + { goto error; } key_length = current_ - key_begin; // There should be at least one blank character after the key string. - if (!blank(*current_)) { + if (!blank(*current_)) + { goto error; } state_ = SkipUntilNot(blank); - if (state_ != State::CAN_CONTINUE) { + if (state_ != State::CAN_CONTINUE) + { goto error; } - if (!content_char(*current_)) { + if (!content_char(*current_)) + { goto error; } @@ -90,9 +106,9 @@ KeyValueBlobReader::State KeyValueBlobReader::Next(KeyValue* out) // like "foo bar baz\n" where baz should not be treated as the Next key. SkipUntil(new_line); - if (out != nullptr) { - *out = KeyValue { std::string_view { key_begin, key_length }, - std::string_view { value_begin, value_length } }; + if (out != nullptr) + { + *out = KeyValue{std::string_view{key_begin, key_length}, std::string_view{value_begin, value_length}}; } state_ = State::HAS_PAIR; return state_; @@ -102,11 +118,12 @@ error: return state_; } -KeyValueBlobReader::State KeyValueBlobReader::SkipUntilNot( - const std::function& f) +KeyValueBlobReader::State KeyValueBlobReader::SkipUntilNot(const std::function &f) { - while (current_ != end_ && *current_) { - if (!f(*current_)) { + while (current_ != end_ && *current_) + { + if (!f(*current_)) + { return State::CAN_CONTINUE; } ++current_; @@ -115,11 +132,12 @@ KeyValueBlobReader::State KeyValueBlobReader::SkipUntilNot( return State::END; } -KeyValueBlobReader::State KeyValueBlobReader::SkipUntil( - const std::function& f) +KeyValueBlobReader::State KeyValueBlobReader::SkipUntil(const std::function &f) { - while (current_ != end_ && *current_) { - if (f(*current_)) { + while (current_ != end_ && *current_) + { + if (f(*current_)) + { return State::CAN_CONTINUE; } ++current_; @@ -128,8 +146,7 @@ KeyValueBlobReader::State KeyValueBlobReader::SkipUntil( return State::END; } -std::ostream& operator<<( - std::ostream& os, const KeyValueBlobReader::KeyValue& kv) +std::ostream &operator<<(std::ostream &os, const KeyValueBlobReader::KeyValue &kv) { os << "(key: " << kv.key << ", value: " << kv.value << ")"; return os; diff --git a/Source/Modules/ControllerModules/KeyValueBlobReader.h b/Source/Modules/ControllerModules/KeyValueBlobReader.h index 5973a34e..8ca313be 100644 --- a/Source/Modules/ControllerModules/KeyValueBlobReader.h +++ b/Source/Modules/ControllerModules/KeyValueBlobReader.h @@ -1,20 +1,27 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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. */ #ifndef SOURCE_ENGINE_KEYVALUEBLOBREADER_H_ @@ -39,11 +46,14 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR TH // std::string_view is used to allow returning results efficiently. As a result, // the blob is a const char* and will never be mutated. This implies, for // example, read-only mmap can be used to parse large files. -namespace vChewing { +namespace vChewing +{ -class KeyValueBlobReader { -public: - enum class State : int { +class KeyValueBlobReader +{ + public: + enum class State : int + { // There are no more key-value pairs in this blob. END = 0, // The reader has produced a new key-value pair. @@ -54,19 +64,16 @@ public: CAN_CONTINUE = 2 }; - struct KeyValue { - constexpr KeyValue() - : key("") - , value("") + struct KeyValue + { + constexpr KeyValue() : key(""), value("") { } - constexpr KeyValue(std::string_view k, std::string_view v) - : key(k) - , value(v) + constexpr KeyValue(std::string_view k, std::string_view v) : key(k), value(v) { } - bool operator==(const KeyValue& another) const + bool operator==(const KeyValue &another) const { return key == another.key && value == another.value; } @@ -75,27 +82,25 @@ public: std::string_view value; }; - KeyValueBlobReader(const char* blob, size_t size) - : current_(blob) - , end_(blob + size) + KeyValueBlobReader(const char *blob, size_t size) : current_(blob), end_(blob + size) { } // Parse the next key-value pair and return the state of the reader. If // `out` is passed, out will be set to the produced key-value pair if there // is one. - State Next(KeyValue* out = nullptr); + State Next(KeyValue *out = nullptr); -private: - State SkipUntil(const std::function& f); - State SkipUntilNot(const std::function& f); + private: + State SkipUntil(const std::function &f); + State SkipUntilNot(const std::function &f); - const char* current_; - const char* end_; + const char *current_; + const char *end_; State state_ = State::CAN_CONTINUE; }; -std::ostream& operator<<(std::ostream&, const KeyValueBlobReader::KeyValue&); +std::ostream &operator<<(std::ostream &, const KeyValueBlobReader::KeyValue &); } // namespace vChewing diff --git a/Source/Modules/FileHandlers/LMConsolidator.h b/Source/Modules/FileHandlers/LMConsolidator.h index 38b25ae8..9bda0d9e 100644 --- a/Source/Modules/FileHandlers/LMConsolidator.h +++ b/Source/Modules/FileHandlers/LMConsolidator.h @@ -1,40 +1,47 @@ // Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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. */ #ifndef LMConsolidator_hpp #define LMConsolidator_hpp -#include -#include #include -#include #include -#include #include -#include #include +#include +#include +#include +#include +#include using namespace std; -namespace vChewing { +namespace vChewing +{ class LMConsolidator { -public: + public: static bool CheckPragma(const char *path); static bool FixEOF(const char *path); static bool ConsolidateContent(const char *path, bool shouldCheckPragma); diff --git a/Source/Modules/FileHandlers/LMConsolidator.mm b/Source/Modules/FileHandlers/LMConsolidator.mm index 8a9a7b4a..0843e93d 100644 --- a/Source/Modules/FileHandlers/LMConsolidator.mm +++ b/Source/Modules/FileHandlers/LMConsolidator.mm @@ -1,28 +1,35 @@ // Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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. */ #include "LMConsolidator.h" #include "vChewing-Swift.h" -namespace vChewing { +namespace vChewing +{ -constexpr std::string_view FORMATTED_PRAGMA_HEADER - = "# 𝙵𝙾𝚁𝙼𝙰𝚃 𝚘𝚛𝚐.𝚊𝚝𝚎𝚕𝚒𝚎𝚛𝙸𝚗𝚖𝚞.𝚟𝚌𝚑𝚎𝚠𝚒𝚗𝚐.𝚞𝚜𝚎𝚛𝙻𝚊𝚗𝚐𝚞𝚊𝚐𝚎𝙼𝚘𝚍𝚎𝚕𝙳𝚊𝚝𝚊.𝚏𝚘𝚛𝚖𝚊𝚝𝚝𝚎𝚍"; +constexpr std::string_view FORMATTED_PRAGMA_HEADER = + "# 𝙵𝙾𝚁𝙼𝙰𝚃 𝚘𝚛𝚐.𝚊𝚝𝚎𝚕𝚒𝚎𝚛𝙸𝚗𝚖𝚞.𝚟𝚌𝚑𝚎𝚠𝚒𝚗𝚐.𝚞𝚜𝚎𝚛𝙻𝚊𝚗𝚐𝚞𝚊𝚐𝚎𝙼𝚘𝚍𝚎𝚕𝙳𝚊𝚝𝚊.𝚏𝚘𝚛𝚖𝚊𝚝𝚝𝚎𝚍"; // HEADER VERIFIER. CREDIT: Shiki Suen bool LMConsolidator::CheckPragma(const char *path) @@ -32,13 +39,17 @@ bool LMConsolidator::CheckPragma(const char *path) { string firstLine; getline(zfdCheckPragma, firstLine); - if (mgrPrefs.isDebugModeEnabled) syslog(LOG_CONS, "HEADER SEEN ||%s", firstLine.c_str()); - if (firstLine != FORMATTED_PRAGMA_HEADER) { - if (mgrPrefs.isDebugModeEnabled) syslog(LOG_CONS, "HEADER VERIFICATION FAILED. START IN-PLACE CONSOLIDATING PROCESS."); + if (mgrPrefs.isDebugModeEnabled) + syslog(LOG_CONS, "HEADER SEEN ||%s", firstLine.c_str()); + if (firstLine != FORMATTED_PRAGMA_HEADER) + { + if (mgrPrefs.isDebugModeEnabled) + syslog(LOG_CONS, "HEADER VERIFICATION FAILED. START IN-PLACE CONSOLIDATING PROCESS."); return false; } } - if (mgrPrefs.isDebugModeEnabled) syslog(LOG_CONS, "HEADER VERIFICATION SUCCESSFUL."); + if (mgrPrefs.isDebugModeEnabled) + syslog(LOG_CONS, "HEADER VERIFICATION SUCCESSFUL."); return true; } @@ -46,58 +57,76 @@ bool LMConsolidator::CheckPragma(const char *path) bool LMConsolidator::FixEOF(const char *path) { std::fstream zfdEOFFixerIncomingStream(path); - zfdEOFFixerIncomingStream.seekg(-1,std::ios_base::end); + zfdEOFFixerIncomingStream.seekg(-1, std::ios_base::end); char z; zfdEOFFixerIncomingStream.get(z); - if(z!='\n'){ - if (mgrPrefs.isDebugModeEnabled) syslog(LOG_CONS, "// REPORT: Data File not ended with a new line.\n"); - if (mgrPrefs.isDebugModeEnabled) syslog(LOG_CONS, "// DATA FILE: %s", path); - if (mgrPrefs.isDebugModeEnabled) syslog(LOG_CONS, "// PROCEDURE: Trying to insert a new line as EOF before per-line check process.\n"); + if (z != '\n') + { + if (mgrPrefs.isDebugModeEnabled) + syslog(LOG_CONS, "// REPORT: Data File not ended with a new line.\n"); + if (mgrPrefs.isDebugModeEnabled) + syslog(LOG_CONS, "// DATA FILE: %s", path); + if (mgrPrefs.isDebugModeEnabled) + syslog(LOG_CONS, "// PROCEDURE: Trying to insert a new line as EOF before per-line check process.\n"); std::ofstream zfdEOFFixerOutput(path, std::ios_base::app); zfdEOFFixerOutput << std::endl; zfdEOFFixerOutput.close(); - if (zfdEOFFixerOutput.fail()) { - if (mgrPrefs.isDebugModeEnabled) syslog(LOG_CONS, "// REPORT: Failed to append a newline to the data file. Insufficient Privileges?\n"); - if (mgrPrefs.isDebugModeEnabled) syslog(LOG_CONS, "// DATA FILE: %s", path); + if (zfdEOFFixerOutput.fail()) + { + if (mgrPrefs.isDebugModeEnabled) + syslog(LOG_CONS, "// REPORT: Failed to append a newline to the data file. Insufficient Privileges?\n"); + if (mgrPrefs.isDebugModeEnabled) + syslog(LOG_CONS, "// DATA FILE: %s", path); return false; } } zfdEOFFixerIncomingStream.close(); - if (zfdEOFFixerIncomingStream.fail()) { - if (mgrPrefs.isDebugModeEnabled) syslog(LOG_CONS, "// REPORT: Failed to read lines through the data file for EOF check. Insufficient Privileges?\n"); - if (mgrPrefs.isDebugModeEnabled) syslog(LOG_CONS, "// DATA FILE: %s", path); + if (zfdEOFFixerIncomingStream.fail()) + { + if (mgrPrefs.isDebugModeEnabled) + syslog(LOG_CONS, + "// REPORT: Failed to read lines through the data file for EOF check. Insufficient Privileges?\n"); + if (mgrPrefs.isDebugModeEnabled) + syslog(LOG_CONS, "// DATA FILE: %s", path); return false; } return true; } // END: EOF FIXER. // CONTENT CONSOLIDATOR. CREDIT: Shiki Suen. -bool LMConsolidator::ConsolidateContent(const char *path, bool shouldCheckPragma) { +bool LMConsolidator::ConsolidateContent(const char *path, bool shouldCheckPragma) +{ bool pragmaCheckResult = LMConsolidator::CheckPragma(path); - if (pragmaCheckResult && shouldCheckPragma){ + if (pragmaCheckResult && shouldCheckPragma) + { return true; } ifstream zfdContentConsolidatorIncomingStream(path); - vectorvecEntry; - while(!zfdContentConsolidatorIncomingStream.eof()) + vector vecEntry; + while (!zfdContentConsolidatorIncomingStream.eof()) { // Xcode 13 能用的 ObjCpp 與 Cpp 並無原生支援「\h」這個 Regex 參數的能力,只能逐行處理。 string zfdBuffer; - getline(zfdContentConsolidatorIncomingStream,zfdBuffer); + getline(zfdContentConsolidatorIncomingStream, zfdBuffer); vecEntry.push_back(zfdBuffer); } // 第一遍 for 用來統整每行內的內容。 - // regex sedCJKWhiteSpace("\\x{3000}"), sedNonBreakWhiteSpace("\\x{A0}"), sedWhiteSpace("\\s+"), sedLeadingSpace("^\\s"), sedTrailingSpace("\\s$"); // 這樣寫會導致輸入法敲不了任何字,推測 Xcode 13 支援的 cpp / objCpp 可能對某些 Regex 寫法有相容性問題。 - // regex sedCJKWhiteSpace(" "), sedNonBreakWhiteSpace(" "), sedWhiteSpace("\\s+"), sedLeadingSpace("^\\s"), sedTrailingSpace("\\s$"); // RegEx 先定義好。 + // regex sedCJKWhiteSpace("\\x{3000}"), sedNonBreakWhiteSpace("\\x{A0}"), sedWhiteSpace("\\s+"), + // sedLeadingSpace("^\\s"), sedTrailingSpace("\\s$"); // 這樣寫會導致輸入法敲不了任何字,推測 Xcode 13 支援的 cpp / + // objCpp 可能對某些 Regex 寫法有相容性問題。 regex sedCJKWhiteSpace(" "), sedNonBreakWhiteSpace(" "), + // sedWhiteSpace("\\s+"), sedLeadingSpace("^\\s"), sedTrailingSpace("\\s$"); // RegEx 先定義好。 regex sedToConsolidate("( +| +| +|\t+)+"), sedToTrim("(^\\s|\\s$)"); - for(int i=0;i #include -namespace vChewing { +namespace vChewing +{ using namespace Gramambular; @@ -57,58 +65,59 @@ using namespace Gramambular; /// model while launching and to load the user phrases anytime if the custom /// files are modified. It does not keep the reference of the data pathes but /// you have to pass the paths when you ask it to do loading. -class LMInstantiator : public Gramambular::LanguageModel { -public: +class LMInstantiator : public Gramambular::LanguageModel +{ + public: LMInstantiator(); ~LMInstantiator(); /// Asks to load the primary language model at the given path. /// @param languageModelPath The 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 primary language model at the given path. /// @param miscDataPath The path of the misc data model. - void loadMiscData(const char* miscDataPath); + void loadMiscData(const char *miscDataPath); /// If the data model is already loaded. bool isMiscDataLoaded(); /// Asks to load the primary language model at the given path. /// @param symbolDataPath The path of the symbol data model. - void loadSymbolData(const char* symbolDataPath); + void loadSymbolData(const char *symbolDataPath); /// If the data model is already loaded. bool isSymbolDataLoaded(); /// Asks to load the primary language model at the given path. /// @param cnsDataPath The path of the CNS data model. - void loadCNSData(const char* cnsDataPath); + void loadCNSData(const char *cnsDataPath); /// If the data model is already loaded. bool isCNSDataLoaded(); /// Asks to load the user phrases and excluded phrases at the given path. /// @param userPhrasesPath The path of user phrases. /// @param excludedPhrasesPath The path of excluded phrases. - void loadUserPhrases(const char* userPhrasesPath, const char* excludedPhrasesPath); + void loadUserPhrases(const char *userPhrasesPath, const char *excludedPhrasesPath); /// Asks to load the user symbol data at the given path. /// @param userSymbolDataPath The path of user symbol data. - void loadUserSymbolData(const char* userPhrasesPath); + void loadUserSymbolData(const char *userPhrasesPath); /// Asks to load the user associated phrases at the given path. /// @param userAssociatedPhrasesPath The path of the user associated phrases. - void loadUserAssociatedPhrases(const char* userAssociatedPhrasesPath); + void loadUserAssociatedPhrases(const char *userAssociatedPhrasesPath); /// Asks to load the phrase replacement table at the given path. /// @param phraseReplacementPath The path of the phrase replacement table. - void loadPhraseReplacementMap(const char* phraseReplacementPath); + void loadPhraseReplacementMap(const char *phraseReplacementPath); /// Not implemented since we do not have data to provide bigram function. - const std::vector bigramsForKeys(const std::string& preceedingKey, const std::string& key); + const std::vector bigramsForKeys(const std::string &preceedingKey, const std::string &key); /// Returns a list of available unigram for the given key. /// @param key A std::string represents the BPMF reading or a symbol key. For /// example, it you pass "ㄇㄚ", it returns "嗎", "媽", and so on. - const std::vector unigramsForKey(const std::string& key); + const std::vector unigramsForKey(const std::string &key); /// If the model has unigrams for the given key. /// @param key The key. - bool hasUnigramsForKey(const std::string& key); + bool hasUnigramsForKey(const std::string &key); /// Enables or disables phrase replacement. void setPhraseReplacementEnabled(bool enabled); @@ -125,21 +134,20 @@ public: /// If CNS11643 input is enabled or not. bool cnsEnabled(); - const std::vector associatedPhrasesForKey(const std::string& key); - bool hasAssociatedPhrasesForKey(const std::string& key); + const std::vector associatedPhrasesForKey(const std::string &key); + bool hasAssociatedPhrasesForKey(const std::string &key); - -protected: + protected: /// Filters and converts the input unigrams and return a new list of unigrams. - /// + /// /// @param unigrams The unigrams to be processed. /// @param excludedValues The values to excluded unigrams. /// @param insertedValues The values for unigrams already in the results. /// It helps to prevent duplicated unigrams. Please note that the method /// has a side effect that it inserts values to `insertedValues`. - const std::vector filterAndTransformUnigrams(const std::vector unigrams, - const std::unordered_set& excludedValues, - std::unordered_set& insertedValues); + const std::vector filterAndTransformUnigrams( + const std::vector unigrams, const std::unordered_set &excludedValues, + std::unordered_set &insertedValues); ParselessLM m_languageModel; CoreLM m_miscModel; @@ -154,6 +162,6 @@ protected: bool m_cnsEnabled; bool m_symbolEnabled; }; -}; +}; // namespace vChewing #endif diff --git a/Source/Modules/LangModelRelated/LMInstantiator.mm b/Source/Modules/LangModelRelated/LMInstantiator.mm index 57f88d7a..2873cbf2 100644 --- a/Source/Modules/LangModelRelated/LMInstantiator.mm +++ b/Source/Modules/LangModelRelated/LMInstantiator.mm @@ -1,27 +1,35 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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. */ #include "LMInstantiator.h" #include #include -namespace vChewing { +namespace vChewing +{ LMInstantiator::LMInstantiator() { @@ -39,9 +47,10 @@ LMInstantiator::~LMInstantiator() m_associatedPhrases.close(); } -void LMInstantiator::loadLanguageModel(const char* languageModelDataPath) +void LMInstantiator::loadLanguageModel(const char *languageModelDataPath) { - if (languageModelDataPath) { + if (languageModelDataPath) + { m_languageModel.close(); m_languageModel.open(languageModelDataPath); } @@ -52,9 +61,10 @@ bool LMInstantiator::isDataModelLoaded() return m_languageModel.isLoaded(); } -void LMInstantiator::loadCNSData(const char* cnsDataPath) +void LMInstantiator::loadCNSData(const char *cnsDataPath) { - if (cnsDataPath) { + if (cnsDataPath) + { m_cnsModel.close(); m_cnsModel.open(cnsDataPath); } @@ -65,9 +75,10 @@ bool LMInstantiator::isCNSDataLoaded() return m_cnsModel.isLoaded(); } -void LMInstantiator::loadMiscData(const char* miscDataPath) +void LMInstantiator::loadMiscData(const char *miscDataPath) { - if (miscDataPath) { + if (miscDataPath) + { m_miscModel.close(); m_miscModel.open(miscDataPath); } @@ -78,9 +89,10 @@ bool LMInstantiator::isMiscDataLoaded() return m_miscModel.isLoaded(); } -void LMInstantiator::loadSymbolData(const char* symbolDataPath) +void LMInstantiator::loadSymbolData(const char *symbolDataPath) { - if (symbolDataPath) { + if (symbolDataPath) + { m_symbolModel.close(); m_symbolModel.open(symbolDataPath); } @@ -91,14 +103,15 @@ bool LMInstantiator::isSymbolDataLoaded() return m_symbolModel.isLoaded(); } -void LMInstantiator::loadUserPhrases(const char* userPhrasesDataPath, - const char* excludedPhrasesDataPath) +void LMInstantiator::loadUserPhrases(const char *userPhrasesDataPath, const char *excludedPhrasesDataPath) { - if (userPhrasesDataPath) { + if (userPhrasesDataPath) + { m_userPhrases.close(); m_userPhrases.open(userPhrasesDataPath); } - if (excludedPhrasesDataPath) { + if (excludedPhrasesDataPath) + { m_excludedPhrases.close(); m_excludedPhrases.open(excludedPhrasesDataPath); } @@ -106,7 +119,8 @@ void LMInstantiator::loadUserPhrases(const char* userPhrasesDataPath, void LMInstantiator::loadUserSymbolData(const char *userSymbolDataPath) { - if (userSymbolDataPath) { + if (userSymbolDataPath) + { m_userSymbolModel.close(); m_userSymbolModel.open(userSymbolDataPath); } @@ -114,28 +128,32 @@ void LMInstantiator::loadUserSymbolData(const char *userSymbolDataPath) void LMInstantiator::loadUserAssociatedPhrases(const char *userAssociatedPhrasesPath) { - if (userAssociatedPhrasesPath) { + if (userAssociatedPhrasesPath) + { m_associatedPhrases.close(); m_associatedPhrases.open(userAssociatedPhrasesPath); } } -void LMInstantiator::loadPhraseReplacementMap(const char* phraseReplacementPath) +void LMInstantiator::loadPhraseReplacementMap(const char *phraseReplacementPath) { - if (phraseReplacementPath) { + if (phraseReplacementPath) + { m_phraseReplacement.close(); m_phraseReplacement.open(phraseReplacementPath); } } -const std::vector LMInstantiator::bigramsForKeys(const std::string& preceedingKey, const std::string& key) +const std::vector LMInstantiator::bigramsForKeys(const std::string &preceedingKey, + const std::string &key) { return std::vector(); } -const std::vector LMInstantiator::unigramsForKey(const std::string& key) +const std::vector LMInstantiator::unigramsForKey(const std::string &key) { - if (key == " ") { + if (key == " ") + { std::vector spaceUnigrams; Gramambular::Unigram g; g.keyValue.key = " "; @@ -152,17 +170,18 @@ const std::vector LMInstantiator::unigramsForKey(const std std::vector userSymbolUnigrams; std::vector cnsUnigrams; - std::unordered_set excludedValues; - std::unordered_set insertedValues; + std::unordered_set excludedValues; + std::unordered_set insertedValues; - if (m_excludedPhrases.hasUnigramsForKey(key)) { + if (m_excludedPhrases.hasUnigramsForKey(key)) + { std::vector excludedUnigrams = m_excludedPhrases.unigramsForKey(key); - transform(excludedUnigrams.begin(), excludedUnigrams.end(), - inserter(excludedValues, excludedValues.end()), - [](const Gramambular::Unigram& u) { return u.keyValue.value; }); + transform(excludedUnigrams.begin(), excludedUnigrams.end(), inserter(excludedValues, excludedValues.end()), + [](const Gramambular::Unigram &u) { return u.keyValue.value; }); } - if (m_userPhrases.hasUnigramsForKey(key)) { + if (m_userPhrases.hasUnigramsForKey(key)) + { std::vector rawUserUnigrams = m_userPhrases.unigramsForKey(key); // 用這句指令讓使用者語彙檔案內的詞條優先順序隨著行數增加而逐漸增高。 // 這樣一來就可以在就地新增語彙時徹底複寫優先權。 @@ -170,27 +189,32 @@ const std::vector LMInstantiator::unigramsForKey(const std userUnigrams = filterAndTransformUnigrams(rawUserUnigrams, excludedValues, insertedValues); } - if (m_languageModel.hasUnigramsForKey(key)) { + if (m_languageModel.hasUnigramsForKey(key)) + { std::vector rawGlobalUnigrams = m_languageModel.unigramsForKey(key); allUnigrams = filterAndTransformUnigrams(rawGlobalUnigrams, excludedValues, insertedValues); } - if (m_miscModel.hasUnigramsForKey(key)) { + if (m_miscModel.hasUnigramsForKey(key)) + { std::vector rawMiscUnigrams = m_miscModel.unigramsForKey(key); miscUnigrams = filterAndTransformUnigrams(rawMiscUnigrams, excludedValues, insertedValues); } - if (m_symbolModel.hasUnigramsForKey(key) && m_symbolEnabled) { + if (m_symbolModel.hasUnigramsForKey(key) && m_symbolEnabled) + { std::vector rawSymbolUnigrams = m_symbolModel.unigramsForKey(key); symbolUnigrams = filterAndTransformUnigrams(rawSymbolUnigrams, excludedValues, insertedValues); } - if (m_userSymbolModel.hasUnigramsForKey(key) && m_symbolEnabled) { + if (m_userSymbolModel.hasUnigramsForKey(key) && m_symbolEnabled) + { std::vector rawUserSymbolUnigrams = m_userSymbolModel.unigramsForKey(key); userSymbolUnigrams = filterAndTransformUnigrams(rawUserSymbolUnigrams, excludedValues, insertedValues); } - if (m_cnsModel.hasUnigramsForKey(key) && m_cnsEnabled) { + if (m_cnsModel.hasUnigramsForKey(key) && m_cnsEnabled) + { std::vector rawCNSUnigrams = m_cnsModel.unigramsForKey(key); cnsUnigrams = filterAndTransformUnigrams(rawCNSUnigrams, excludedValues, insertedValues); } @@ -203,13 +227,15 @@ const std::vector LMInstantiator::unigramsForKey(const std return allUnigrams; } -bool LMInstantiator::hasUnigramsForKey(const std::string& key) +bool LMInstantiator::hasUnigramsForKey(const std::string &key) { - if (key == " ") { + if (key == " ") + { return true; } - if (!m_excludedPhrases.hasUnigramsForKey(key)) { + if (!m_excludedPhrases.hasUnigramsForKey(key)) + { return m_userPhrases.hasUnigramsForKey(key) || m_languageModel.hasUnigramsForKey(key); } @@ -246,26 +272,33 @@ bool LMInstantiator::symbolEnabled() return m_symbolEnabled; } -const std::vector LMInstantiator::filterAndTransformUnigrams(const std::vector unigrams, const std::unordered_set& excludedValues, std::unordered_set& insertedValues) +const std::vector LMInstantiator::filterAndTransformUnigrams( + const std::vector unigrams, const std::unordered_set &excludedValues, + std::unordered_set &insertedValues) { std::vector results; - for (auto&& unigram : unigrams) { + for (auto &&unigram : unigrams) + { // excludedValues filters out the unigrams with the original value. // insertedValues filters out the ones with the converted value std::string originalValue = unigram.keyValue.value; - if (excludedValues.find(originalValue) != excludedValues.end()) { + if (excludedValues.find(originalValue) != excludedValues.end()) + { continue; } std::string value = originalValue; - if (m_phraseReplacementEnabled) { + if (m_phraseReplacementEnabled) + { std::string replacement = m_phraseReplacement.valueForKey(value); - if (replacement != "") { + if (replacement != "") + { value = replacement; } } - if (insertedValues.find(value) == insertedValues.end()) { + if (insertedValues.find(value) == insertedValues.end()) + { Gramambular::Unigram g; g.keyValue.value = value; g.keyValue.key = unigram.keyValue.key; @@ -277,12 +310,12 @@ const std::vector LMInstantiator::filterAndTransformUnigra return results; } -const std::vector LMInstantiator::associatedPhrasesForKey(const std::string& key) +const std::vector LMInstantiator::associatedPhrasesForKey(const std::string &key) { return m_associatedPhrases.valuesForKey(key); } -bool LMInstantiator::hasAssociatedPhrasesForKey(const std::string& key) +bool LMInstantiator::hasAssociatedPhrasesForKey(const std::string &key) { return m_associatedPhrases.hasValuesForKey(key); } diff --git a/Source/Modules/LangModelRelated/SubLanguageModels/AssociatedPhrases.h b/Source/Modules/LangModelRelated/SubLanguageModels/AssociatedPhrases.h index 98978b2a..63f6aca1 100644 --- a/Source/Modules/LangModelRelated/SubLanguageModels/AssociatedPhrases.h +++ b/Source/Modules/LangModelRelated/SubLanguageModels/AssociatedPhrases.h @@ -1,47 +1,58 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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. */ #ifndef ASSOCIATEDPHRASES_H #define ASSOCIATEDPHRASES_H -#include -#include #include +#include +#include #include -namespace vChewing { +namespace vChewing +{ class AssociatedPhrases { -public: + public: AssociatedPhrases(); ~AssociatedPhrases(); const bool isLoaded(); bool open(const char *path); void close(); - const std::vector valuesForKey(const std::string& key); - const bool hasValuesForKey(const std::string& key); + const std::vector valuesForKey(const std::string &key); + const bool hasValuesForKey(const std::string &key); -protected: - struct Row { - Row(std::string_view& k, std::string_view& v) : key(k), value(v) {} + protected: + struct Row + { + Row(std::string_view &k, std::string_view &v) : key(k), value(v) + { + } std::string_view key; std::string_view value; }; @@ -53,6 +64,6 @@ protected: size_t length; }; -} +} // namespace vChewing #endif /* AssociatedPhrases_hpp */ diff --git a/Source/Modules/LangModelRelated/SubLanguageModels/AssociatedPhrases.mm b/Source/Modules/LangModelRelated/SubLanguageModels/AssociatedPhrases.mm index 430df6e7..ac0f223e 100644 --- a/Source/Modules/LangModelRelated/SubLanguageModels/AssociatedPhrases.mm +++ b/Source/Modules/LangModelRelated/SubLanguageModels/AssociatedPhrases.mm @@ -1,52 +1,59 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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. */ #include "AssociatedPhrases.h" #include "vChewing-Swift.h" -#include -#include #include #include +#include +#include #include #include "KeyValueBlobReader.h" #include "LMConsolidator.h" -namespace vChewing { +namespace vChewing +{ -AssociatedPhrases::AssociatedPhrases() -: fd(-1) -, data(0) -, length(0) +AssociatedPhrases::AssociatedPhrases() : fd(-1), data(0), length(0) { } AssociatedPhrases::~AssociatedPhrases() { - if (data) { + if (data) + { close(); } } const bool AssociatedPhrases::isLoaded() { - if (data) { + if (data) + { return true; } return false; @@ -54,7 +61,8 @@ const bool AssociatedPhrases::isLoaded() bool AssociatedPhrases::open(const char *path) { - if (data) { + if (data) + { return false; } @@ -62,13 +70,15 @@ bool AssociatedPhrases::open(const char *path) LMConsolidator::ConsolidateContent(path, true); fd = ::open(path, O_RDONLY); - if (fd == -1) { + if (fd == -1) + { printf("open:: file not exist"); return false; } struct stat sb; - if (fstat(fd, &sb) == -1) { + if (fstat(fd, &sb) == -1) + { printf("open:: cannot open file"); return false; } @@ -76,21 +86,25 @@ bool AssociatedPhrases::open(const char *path) length = (size_t)sb.st_size; data = mmap(NULL, length, PROT_READ, MAP_SHARED, fd, 0); - if (!data) { + if (!data) + { ::close(fd); return false; } - KeyValueBlobReader reader(static_cast(data), length); + KeyValueBlobReader reader(static_cast(data), length); KeyValueBlobReader::KeyValue keyValue; KeyValueBlobReader::State state; - while ((state = reader.Next(&keyValue)) == KeyValueBlobReader::State::HAS_PAIR) { + while ((state = reader.Next(&keyValue)) == KeyValueBlobReader::State::HAS_PAIR) + { keyRowMap[keyValue.key].emplace_back(keyValue.key, keyValue.value); } // 下面這一段或許可以做成開關、來詢問是否對使用者語彙採取寬鬆策略(哪怕有行內容寫錯也會放行) - if (state == KeyValueBlobReader::State::ERROR) { + if (state == KeyValueBlobReader::State::ERROR) + { // close(); - if (mgrPrefs.isDebugModeEnabled) syslog(LOG_CONS, "AssociatedPhrases: Failed at Open Step 5. On Error Resume Next.\n"); + if (mgrPrefs.isDebugModeEnabled) + syslog(LOG_CONS, "AssociatedPhrases: Failed at Open Step 5. On Error Resume Next.\n"); // return false; } return true; @@ -98,7 +112,8 @@ bool AssociatedPhrases::open(const char *path) void AssociatedPhrases::close() { - if (data) { + if (data) + { munmap(data, length); ::close(fd); data = 0; @@ -107,13 +122,15 @@ void AssociatedPhrases::close() keyRowMap.clear(); } -const std::vector AssociatedPhrases::valuesForKey(const std::string& key) +const std::vector AssociatedPhrases::valuesForKey(const std::string &key) { std::vector v; auto iter = keyRowMap.find(key); - if (iter != keyRowMap.end()) { - const std::vector& rows = iter->second; - for (const auto& row : rows) { + if (iter != keyRowMap.end()) + { + const std::vector &rows = iter->second; + for (const auto &row : rows) + { std::string_view value = row.value; v.push_back({value.data(), value.size()}); } @@ -121,9 +138,9 @@ const std::vector AssociatedPhrases::valuesForKey(const std::string return v; } -const bool AssociatedPhrases::hasValuesForKey(const std::string& key) +const bool AssociatedPhrases::hasValuesForKey(const std::string &key) { return keyRowMap.find(key) != keyRowMap.end(); } -}; // namespace vChewing +}; // namespace vChewing diff --git a/Source/Modules/LangModelRelated/SubLanguageModels/CoreLM.h b/Source/Modules/LangModelRelated/SubLanguageModels/CoreLM.h index 0beffc83..46625e74 100644 --- a/Source/Modules/LangModelRelated/SubLanguageModels/CoreLM.h +++ b/Source/Modules/LangModelRelated/SubLanguageModels/CoreLM.h @@ -1,30 +1,37 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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. */ #ifndef CoreLM_H #define CoreLM_H #include "LanguageModel.h" +#include +#include #include #include -#include -#include // this class relies on the fact that we have a space-separated data // format, and we use mmap and zero-out the separators and line feeds @@ -33,10 +40,12 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR TH using namespace std; using namespace Gramambular; -namespace vChewing { +namespace vChewing +{ -class CoreLM : public Gramambular::LanguageModel { -public: +class CoreLM : public Gramambular::LanguageModel +{ + public: CoreLM(); ~CoreLM(); @@ -45,20 +54,21 @@ public: void close(); void dump(); - virtual const std::vector bigramsForKeys(const string& preceedingKey, const string& key); - virtual const std::vector unigramsForKey(const string& key); - virtual bool hasUnigramsForKey(const string& key); + virtual const std::vector bigramsForKeys(const string &preceedingKey, const string &key); + virtual const std::vector unigramsForKey(const string &key); + virtual bool hasUnigramsForKey(const string &key); -protected: + protected: struct CStringCmp { - bool operator()(const char* s1, const char* s2) const + bool operator()(const char *s1, const char *s2) const { return strcmp(s1, s2) < 0; } }; - struct Row { + struct Row + { const char *key; const char *value; const char *logProbability; diff --git a/Source/Modules/LangModelRelated/SubLanguageModels/CoreLM.mm b/Source/Modules/LangModelRelated/SubLanguageModels/CoreLM.mm index b9fae8fd..de24f821 100644 --- a/Source/Modules/LangModelRelated/SubLanguageModels/CoreLM.mm +++ b/Source/Modules/LangModelRelated/SubLanguageModels/CoreLM.mm @@ -1,50 +1,56 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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. */ #include "CoreLM.h" -#include -#include +#include "vChewing-Swift.h" #include #include -#include +#include +#include #include -#include "vChewing-Swift.h" +#include using namespace Gramambular; -vChewing::CoreLM::CoreLM() - : fd(-1) - , data(0) - , length(0) +vChewing::CoreLM::CoreLM() : fd(-1), data(0), length(0) { } vChewing::CoreLM::~CoreLM() { - if (data) { + if (data) + { close(); } } bool vChewing::CoreLM::isLoaded() { - if (data) { + if (data) + { return true; } return false; @@ -52,24 +58,28 @@ bool vChewing::CoreLM::isLoaded() bool vChewing::CoreLM::open(const char *path) { - if (data) { + if (data) + { return false; } - + fd = ::open(path, O_RDONLY); - if (fd == -1) { + if (fd == -1) + { return false; } struct stat sb; - if (fstat(fd, &sb) == -1) { + if (fstat(fd, &sb) == -1) + { return false; } length = (size_t)sb.st_size; data = mmap(NULL, length, PROT_WRITE, MAP_PRIVATE, fd, 0); - if (!data) { + if (!data) + { ::close(fd); return false; } @@ -117,18 +127,22 @@ bool vChewing::CoreLM::open(const char *path) start: // EOF -> end - if (head == end) { + if (head == end) + { goto end; } c = *head; // \s -> error - if (c == ' ') { - if (mgrPrefs.isDebugModeEnabled) syslog(LOG_CONS, "vChewingDebug: CoreLM // Start: \\s -> error"); + if (c == ' ') + { + if (mgrPrefs.isDebugModeEnabled) + syslog(LOG_CONS, "vChewingDebug: CoreLM // Start: \\s -> error"); goto error; } // \n -> start - else if (c == '\n') { + else if (c == '\n') + { head++; goto start; } @@ -140,19 +154,24 @@ start: state1: // EOF -> error - if (head == end) { - if (mgrPrefs.isDebugModeEnabled) syslog(LOG_CONS, "vChewingDebug: CoreLM // state 1: EOF -> error"); + if (head == end) + { + if (mgrPrefs.isDebugModeEnabled) + syslog(LOG_CONS, "vChewingDebug: CoreLM // state 1: EOF -> error"); goto error; } c = *head; // \n -> error - if (c == '\n') { - if (mgrPrefs.isDebugModeEnabled) syslog(LOG_CONS, "vChewingDebug: CoreLM // state 1: \\n -> error"); + if (c == '\n') + { + if (mgrPrefs.isDebugModeEnabled) + syslog(LOG_CONS, "vChewingDebug: CoreLM // state 1: \\n -> error"); goto error; } // \s -> state2 + zero out ending + record column start - else if (c == ' ') { + else if (c == ' ') + { *head = 0; head++; row.key = head; @@ -165,15 +184,19 @@ state1: state2: // eof -> error - if (head == end) { - if (mgrPrefs.isDebugModeEnabled) syslog(LOG_CONS, "vChewingDebug: CoreLM // state 2: EOF -> error"); + if (head == end) + { + if (mgrPrefs.isDebugModeEnabled) + syslog(LOG_CONS, "vChewingDebug: CoreLM // state 2: EOF -> error"); goto error; } c = *head; // \n, \s -> error - if (c == '\n' || c == ' ') { - if (mgrPrefs.isDebugModeEnabled) syslog(LOG_CONS, "vChewingDebug: CoreLM // state 2: \\n \\s -> error"); + if (c == '\n' || c == ' ') + { + if (mgrPrefs.isDebugModeEnabled) + syslog(LOG_CONS, "vChewingDebug: CoreLM // state 2: \\n \\s -> error"); goto error; } @@ -184,20 +207,25 @@ state2: state3: // eof -> error - if (head == end) { - if (mgrPrefs.isDebugModeEnabled) syslog(LOG_CONS, "vChewingDebug: CoreLM // state 3: EOF -> error"); + if (head == end) + { + if (mgrPrefs.isDebugModeEnabled) + syslog(LOG_CONS, "vChewingDebug: CoreLM // state 3: EOF -> error"); goto error; } c = *head; // \n -> error - if (c == '\n') { - if (mgrPrefs.isDebugModeEnabled) syslog(LOG_CONS, "vChewingDebug: CoreLM // state 3: \\n -> error"); + if (c == '\n') + { + if (mgrPrefs.isDebugModeEnabled) + syslog(LOG_CONS, "vChewingDebug: CoreLM // state 3: \\n -> error"); goto error; } // \s -> state4 + zero out ending + record column start - else if (c == ' ') { + else if (c == ' ') + { *head = 0; head++; row.logProbability = head; @@ -210,15 +238,19 @@ state3: state4: // eof -> error - if (head == end) { - if (mgrPrefs.isDebugModeEnabled) syslog(LOG_CONS, "vChewingDebug: CoreLM // state 4: EOF -> error"); + if (head == end) + { + if (mgrPrefs.isDebugModeEnabled) + syslog(LOG_CONS, "vChewingDebug: CoreLM // state 4: EOF -> error"); goto error; } c = *head; // \n, \s -> error - if (c == '\n' || c == ' ') { - if (mgrPrefs.isDebugModeEnabled) syslog(LOG_CONS, "vChewingDebug: CoreLM // state 4: \\n \\s -> error"); + if (c == '\n' || c == ' ') + { + if (mgrPrefs.isDebugModeEnabled) + syslog(LOG_CONS, "vChewingDebug: CoreLM // state 4: \\n \\s -> error"); goto error; } @@ -227,22 +259,26 @@ state4: // fall through to state 5 - state5: // eof -> error - if (head == end) { - if (mgrPrefs.isDebugModeEnabled) syslog(LOG_CONS, "vChewingDebug: CoreLM // state 5: EOF -> error"); + if (head == end) + { + if (mgrPrefs.isDebugModeEnabled) + syslog(LOG_CONS, "vChewingDebug: CoreLM // state 5: EOF -> error"); goto error; } c = *head; // \s -> error - if (c == ' ') { - if (mgrPrefs.isDebugModeEnabled) syslog(LOG_CONS, "vChewingDebug: CoreLM // state 5: \\s -> error"); + if (c == ' ') + { + if (mgrPrefs.isDebugModeEnabled) + syslog(LOG_CONS, "vChewingDebug: CoreLM // state 5: \\s -> error"); goto error; } // \n -> start - else if (c == '\n') { + else if (c == '\n') + { *head = 0; head++; keyRowMap[row.key].push_back(row); @@ -265,13 +301,15 @@ end: emptyRow.value = space; emptyRow.logProbability = zero; keyRowMap[space].push_back(emptyRow); - if (mgrPrefs.isDebugModeEnabled) syslog(LOG_CONS, "vChewingDebug: CoreLM // File Load Complete."); + if (mgrPrefs.isDebugModeEnabled) + syslog(LOG_CONS, "vChewingDebug: CoreLM // File Load Complete."); return true; } void vChewing::CoreLM::close() { - if (data) { + if (data) + { munmap(data, length); ::close(fd); data = 0; @@ -283,30 +321,34 @@ void vChewing::CoreLM::close() void vChewing::CoreLM::dump() { size_t rows = 0; - for (map >::const_iterator i = keyRowMap.begin(), e = keyRowMap.end(); i != e; ++i) { - const vector& r = (*i).second; - for (vector::const_iterator ri = r.begin(), re = r.end(); ri != re; ++ri) { - const Row& row = *ri; + for (map>::const_iterator i = keyRowMap.begin(), e = keyRowMap.end(); i != e; ++i) + { + const vector &r = (*i).second; + for (vector::const_iterator ri = r.begin(), re = r.end(); ri != re; ++ri) + { + const Row &row = *ri; cerr << row.key << " " << row.value << " " << row.logProbability << "\n"; rows++; } } } -const std::vector vChewing::CoreLM::bigramsForKeys(const string& preceedingKey, const string& key) +const std::vector vChewing::CoreLM::bigramsForKeys(const string &preceedingKey, const string &key) { return std::vector(); } -const std::vector vChewing::CoreLM::unigramsForKey(const string& key) +const std::vector vChewing::CoreLM::unigramsForKey(const string &key) { std::vector v; - map >::const_iterator i = keyRowMap.find(key.c_str()); + map>::const_iterator i = keyRowMap.find(key.c_str()); - if (i != keyRowMap.end()) { - for (vector::const_iterator ri = (*i).second.begin(), re = (*i).second.end(); ri != re; ++ri) { + if (i != keyRowMap.end()) + { + for (vector::const_iterator ri = (*i).second.begin(), re = (*i).second.end(); ri != re; ++ri) + { Unigram g; - const Row& r = *ri; + const Row &r = *ri; g.keyValue.key = r.key; g.keyValue.value = r.value; g.score = atof(r.logProbability); @@ -317,7 +359,7 @@ const std::vector vChewing::CoreLM::unigramsForKey(const s return v; } -bool vChewing::CoreLM::hasUnigramsForKey(const string& key) +bool vChewing::CoreLM::hasUnigramsForKey(const string &key) { return keyRowMap.find(key.c_str()) != keyRowMap.end(); } diff --git a/Source/Modules/LangModelRelated/SubLanguageModels/InstantiatedModels/CNSLM.h b/Source/Modules/LangModelRelated/SubLanguageModels/InstantiatedModels/CNSLM.h index 80c47b96..6154330a 100644 --- a/Source/Modules/LangModelRelated/SubLanguageModels/InstantiatedModels/CNSLM.h +++ b/Source/Modules/LangModelRelated/SubLanguageModels/InstantiatedModels/CNSLM.h @@ -1,44 +1,54 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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. */ #ifndef CNSLM_H #define CNSLM_H -#include -#include -#include #include "LanguageModel.h" #include "UserPhrasesLM.h" +#include +#include +#include -namespace vChewing { - -class CNSLM: public UserPhrasesLM +namespace vChewing { -public: - virtual bool allowConsolidation() override { + +class CNSLM : public UserPhrasesLM +{ + public: + virtual bool allowConsolidation() override + { return false; } - virtual float overridedValue() override { + virtual float overridedValue() override + { return -11.0; } }; -} +} // namespace vChewing #endif diff --git a/Source/Modules/LangModelRelated/SubLanguageModels/InstantiatedModels/SymbolLM.h b/Source/Modules/LangModelRelated/SubLanguageModels/InstantiatedModels/SymbolLM.h index 48409bc4..78d2a5b7 100644 --- a/Source/Modules/LangModelRelated/SubLanguageModels/InstantiatedModels/SymbolLM.h +++ b/Source/Modules/LangModelRelated/SubLanguageModels/InstantiatedModels/SymbolLM.h @@ -1,44 +1,54 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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. */ #ifndef SYMBOLLM_H #define SYMBOLLM_H -#include -#include -#include #include "LanguageModel.h" #include "UserPhrasesLM.h" +#include +#include +#include -namespace vChewing { - -class SymbolLM: public UserPhrasesLM +namespace vChewing { -public: - virtual bool allowConsolidation() override { + +class SymbolLM : public UserPhrasesLM +{ + public: + virtual bool allowConsolidation() override + { return false; } - virtual float overridedValue() override { + virtual float overridedValue() override + { return -13.0; } }; -} +} // namespace vChewing #endif diff --git a/Source/Modules/LangModelRelated/SubLanguageModels/InstantiatedModels/UserSymbolLM.h b/Source/Modules/LangModelRelated/SubLanguageModels/InstantiatedModels/UserSymbolLM.h index 96ee02d5..7d8646cc 100644 --- a/Source/Modules/LangModelRelated/SubLanguageModels/InstantiatedModels/UserSymbolLM.h +++ b/Source/Modules/LangModelRelated/SubLanguageModels/InstantiatedModels/UserSymbolLM.h @@ -1,44 +1,54 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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. */ #ifndef USERSYMBOLLM_H #define USERSYMBOLLM_H -#include -#include -#include #include "LanguageModel.h" #include "UserPhrasesLM.h" +#include +#include +#include -namespace vChewing { - -class UserSymbolLM: public UserPhrasesLM +namespace vChewing { -public: - virtual bool allowConsolidation() override { + +class UserSymbolLM : public UserPhrasesLM +{ + public: + virtual bool allowConsolidation() override + { return true; } - virtual float overridedValue() override { + virtual float overridedValue() override + { return -12.0; } }; -} +} // namespace vChewing #endif diff --git a/Source/Modules/LangModelRelated/SubLanguageModels/ParselessLM.cpp b/Source/Modules/LangModelRelated/SubLanguageModels/ParselessLM.cpp index 92104c22..4f40bb8c 100644 --- a/Source/Modules/LangModelRelated/SubLanguageModels/ParselessLM.cpp +++ b/Source/Modules/LangModelRelated/SubLanguageModels/ParselessLM.cpp @@ -1,20 +1,27 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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. */ #include "ParselessLM.h" @@ -26,29 +33,36 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR TH #include -vChewing::ParselessLM::~ParselessLM() { close(); } +vChewing::ParselessLM::~ParselessLM() +{ + close(); +} bool vChewing::ParselessLM::isLoaded() { - if (data_) { + if (data_) + { return true; } return false; } -bool vChewing::ParselessLM::open(const std::string_view& path) +bool vChewing::ParselessLM::open(const std::string_view &path) { - if (data_) { + if (data_) + { return false; } fd_ = ::open(path.data(), O_RDONLY); - if (fd_ == -1) { + if (fd_ == -1) + { return false; } struct stat sb; - if (fstat(fd_, &sb) == -1) { + if (fstat(fd_, &sb) == -1) + { ::close(fd_); fd_ = -1; return false; @@ -57,21 +71,22 @@ bool vChewing::ParselessLM::open(const std::string_view& path) length_ = static_cast(sb.st_size); data_ = mmap(NULL, length_, PROT_READ, MAP_SHARED, fd_, 0); - if (data_ == nullptr) { + if (data_ == nullptr) + { ::close(fd_); fd_ = -1; length_ = 0; return false; } - db_ = std::unique_ptr(new ParselessPhraseDB( - static_cast(data_), length_)); + db_ = std::unique_ptr(new ParselessPhraseDB(static_cast(data_), length_)); return true; } void vChewing::ParselessLM::close() { - if (data_ != nullptr) { + if (data_ != nullptr) + { munmap(data_, length_); ::close(fd_); fd_ = -1; @@ -80,55 +95,61 @@ void vChewing::ParselessLM::close() } } -const std::vector -vChewing::ParselessLM::bigramsForKeys( - const std::string& preceedingKey, const std::string& key) +const std::vector vChewing::ParselessLM::bigramsForKeys(const std::string &preceedingKey, + const std::string &key) { return std::vector(); } -const std::vector -vChewing::ParselessLM::unigramsForKey(const std::string& key) +const std::vector vChewing::ParselessLM::unigramsForKey(const std::string &key) { - if (db_ == nullptr) { + if (db_ == nullptr) + { return std::vector(); } std::vector results; - for (const auto& row : db_->findRows(key + " ")) { + for (const auto &row : db_->findRows(key + " ")) + { Gramambular::Unigram unigram; // Move ahead until we encounter the first space. This is the key. auto it = row.begin(); - while (it != row.end() && *it != ' ') { + while (it != row.end() && *it != ' ') + { ++it; } unigram.keyValue.key = std::string(row.begin(), it); // Read past the space. - if (it != row.end()) { + if (it != row.end()) + { ++it; } - if (it != row.end()) { + if (it != row.end()) + { // Now it is the start of the value portion. auto value_begin = it; // Move ahead until we encounter the second space. This is the // value. - while (it != row.end() && *it != ' ') { + while (it != row.end() && *it != ' ') + { ++it; } unigram.keyValue.value = std::string(value_begin, it); } // Read past the space. The remainder, if it exists, is the score. - if (it != row.end()) { + if (it != row.end()) + { ++it; } - if (it != row.end()) { + if (it != row.end()) + { unigram.score = std::stod(std::string(it, row.end())); } results.push_back(unigram); @@ -136,9 +157,10 @@ vChewing::ParselessLM::unigramsForKey(const std::string& key) return results; } -bool vChewing::ParselessLM::hasUnigramsForKey(const std::string& key) +bool vChewing::ParselessLM::hasUnigramsForKey(const std::string &key) { - if (db_ == nullptr) { + if (db_ == nullptr) + { return false; } diff --git a/Source/Modules/LangModelRelated/SubLanguageModels/ParselessLM.h b/Source/Modules/LangModelRelated/SubLanguageModels/ParselessLM.h index 984054a2..698bcecc 100644 --- a/Source/Modules/LangModelRelated/SubLanguageModels/ParselessLM.h +++ b/Source/Modules/LangModelRelated/SubLanguageModels/ParselessLM.h @@ -1,20 +1,27 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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. */ #ifndef SOURCE_ENGINE_PARSELESSLM_H_ @@ -27,25 +34,26 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR TH #include "LanguageModel.h" #include "ParselessPhraseDB.h" -namespace vChewing { +namespace vChewing +{ -class ParselessLM : public Gramambular::LanguageModel { -public: +class ParselessLM : public Gramambular::LanguageModel +{ + public: ~ParselessLM() override; bool isLoaded(); - bool open(const std::string_view& path); + bool open(const std::string_view &path); void close(); - const std::vector bigramsForKeys( - const std::string& preceedingKey, const std::string& key) override; - const std::vector unigramsForKey( - const std::string& key) override; - bool hasUnigramsForKey(const std::string& key) override; + const std::vector bigramsForKeys(const std::string &preceedingKey, + const std::string &key) override; + const std::vector unigramsForKey(const std::string &key) override; + bool hasUnigramsForKey(const std::string &key) override; -private: + private: int fd_ = -1; - void* data_ = nullptr; + void *data_ = nullptr; size_t length_ = 0; std::unique_ptr db_; }; diff --git a/Source/Modules/LangModelRelated/SubLanguageModels/ParselessPhraseDB.cpp b/Source/Modules/LangModelRelated/SubLanguageModels/ParselessPhraseDB.cpp index c28f33b4..a0097e69 100644 --- a/Source/Modules/LangModelRelated/SubLanguageModels/ParselessPhraseDB.cpp +++ b/Source/Modules/LangModelRelated/SubLanguageModels/ParselessPhraseDB.cpp @@ -1,20 +1,27 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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. */ #include "ParselessPhraseDB.h" @@ -22,35 +29,35 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR TH #include #include -namespace vChewing { +namespace vChewing +{ -ParselessPhraseDB::ParselessPhraseDB( - const char* buf, size_t length) - : begin_(buf) - , end_(buf + length) +ParselessPhraseDB::ParselessPhraseDB(const char *buf, size_t length) : begin_(buf), end_(buf + length) { } -std::vector ParselessPhraseDB::findRows( - const std::string_view& key) +std::vector ParselessPhraseDB::findRows(const std::string_view &key) { std::vector rows; - const char* ptr = findFirstMatchingLine(key); - if (ptr == nullptr) { + const char *ptr = findFirstMatchingLine(key); + if (ptr == nullptr) + { return rows; } - while (ptr + key.length() <= end_ - && memcmp(ptr, key.data(), key.length()) == 0) { - const char* eol = ptr; + while (ptr + key.length() <= end_ && memcmp(ptr, key.data(), key.length()) == 0) + { + const char *eol = ptr; - while (eol != end_ && *eol != '\n') { + while (eol != end_ && *eol != '\n') + { ++eol; } rows.emplace_back(ptr, eol - ptr); - if (eol == end_) { + if (eol == end_) + { break; } @@ -66,71 +73,83 @@ std::vector ParselessPhraseDB::findRows( // current line is actually the first matching line: if the previous line is // less to the key and the current line starts exactly with the key, then // the current line is the first matching line. -const char* ParselessPhraseDB::findFirstMatchingLine( - const std::string_view& key) +const char *ParselessPhraseDB::findFirstMatchingLine(const std::string_view &key) { - if (key.empty()) { + if (key.empty()) + { return begin_; } - const char* top = begin_; - const char* bottom = end_; + const char *top = begin_; + const char *bottom = end_; - while (top < bottom) { - const char* mid = top + (bottom - top) / 2; - const char* ptr = mid; + while (top < bottom) + { + const char *mid = top + (bottom - top) / 2; + const char *ptr = mid; - if (ptr != begin_) { + if (ptr != begin_) + { --ptr; } - while (ptr != begin_ && *ptr != '\n') { + while (ptr != begin_ && *ptr != '\n') + { --ptr; } - const char* prev = nullptr; - if (*ptr == '\n') { + const char *prev = nullptr; + if (*ptr == '\n') + { prev = ptr; ++ptr; } // ptr is now in the "current" line we're interested in. - if (ptr + key.length() > end_) { + if (ptr + key.length() > end_) + { // not enough data to compare at this point, bail. break; } int current_cmp = memcmp(ptr, key.data(), key.length()); - if (current_cmp > 0) { + if (current_cmp > 0) + { bottom = mid - 1; continue; } - if (current_cmp < 0) { + if (current_cmp < 0) + { top = mid + 1; continue; } - if (!prev) { + if (!prev) + { return ptr; } // Move the prev so that it reaches the previous line. - if (prev != begin_) { + if (prev != begin_) + { --prev; } - while (prev != begin_ && *prev != '\n') { + while (prev != begin_ && *prev != '\n') + { --prev; } - if (*prev == '\n') { + if (*prev == '\n') + { ++prev; } int prev_cmp = memcmp(prev, key.data(), key.length()); // This is the first occurrence. - if (prev_cmp < 0 && current_cmp == 0) { + if (prev_cmp < 0 && current_cmp == 0) + { return ptr; } diff --git a/Source/Modules/LangModelRelated/SubLanguageModels/ParselessPhraseDB.h b/Source/Modules/LangModelRelated/SubLanguageModels/ParselessPhraseDB.h index d632e653..3ac28768 100644 --- a/Source/Modules/LangModelRelated/SubLanguageModels/ParselessPhraseDB.h +++ b/Source/Modules/LangModelRelated/SubLanguageModels/ParselessPhraseDB.h @@ -1,20 +1,27 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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. */ #ifndef SOURCE_ENGINE_PARSELESSPHRASEDB_H_ @@ -24,28 +31,29 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR TH #include #include -namespace vChewing { +namespace vChewing +{ // Defines phrase database that consists of (key, value, score) rows that are // pre-sorted by the byte value of the keys. It is way faster than FastLM // because it does not need to parse anything. Instead, it relies on the fact // that the database is already sorted, and binary search is used to find the // rows. -class ParselessPhraseDB { -public: - ParselessPhraseDB( - const char* buf, size_t length); +class ParselessPhraseDB +{ + public: + ParselessPhraseDB(const char *buf, size_t length); // Find the rows that match the key. Note that prefix match is used. If you // need exact match, the key will need to have a delimiter (usually a space) // at the end. - std::vector findRows(const std::string_view& key); + std::vector findRows(const std::string_view &key); - const char* findFirstMatchingLine(const std::string_view& key); + const char *findFirstMatchingLine(const std::string_view &key); -private: - const char* begin_; - const char* end_; + private: + const char *begin_; + const char *end_; }; }; // namespace vChewing diff --git a/Source/Modules/LangModelRelated/SubLanguageModels/PhraseReplacementMap.h b/Source/Modules/LangModelRelated/SubLanguageModels/PhraseReplacementMap.h index e4c61b60..43263923 100644 --- a/Source/Modules/LangModelRelated/SubLanguageModels/PhraseReplacementMap.h +++ b/Source/Modules/LangModelRelated/SubLanguageModels/PhraseReplacementMap.h @@ -1,48 +1,56 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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. */ #ifndef PHRASEREPLACEMENTMAP_H #define PHRASEREPLACEMENTMAP_H -#include -#include #include +#include +#include -namespace vChewing { +namespace vChewing +{ class PhraseReplacementMap { -public: + public: PhraseReplacementMap(); ~PhraseReplacementMap(); bool open(const char *path); void close(); - const std::string valueForKey(const std::string& key); + const std::string valueForKey(const std::string &key); -protected: + protected: std::map keyValueMap; int fd; void *data; size_t length; }; -} +} // namespace vChewing #endif diff --git a/Source/Modules/LangModelRelated/SubLanguageModels/PhraseReplacementMap.mm b/Source/Modules/LangModelRelated/SubLanguageModels/PhraseReplacementMap.mm index b3a10e49..7fde339b 100644 --- a/Source/Modules/LangModelRelated/SubLanguageModels/PhraseReplacementMap.mm +++ b/Source/Modules/LangModelRelated/SubLanguageModels/PhraseReplacementMap.mm @@ -1,55 +1,62 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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. */ #include "PhraseReplacementMap.h" #include "vChewing-Swift.h" -#include -#include #include #include -#include +#include +#include #include +#include #include "KeyValueBlobReader.h" #include "LMConsolidator.h" -namespace vChewing { +namespace vChewing +{ using std::string; -PhraseReplacementMap::PhraseReplacementMap() -: fd(-1) -, data(0) -, length(0) +PhraseReplacementMap::PhraseReplacementMap() : fd(-1), data(0), length(0) { } PhraseReplacementMap::~PhraseReplacementMap() { - if (data) { + if (data) + { close(); } } bool PhraseReplacementMap::open(const char *path) { - if (data) { + if (data) + { return false; } @@ -57,13 +64,15 @@ bool PhraseReplacementMap::open(const char *path) LMConsolidator::ConsolidateContent(path, true); fd = ::open(path, O_RDONLY); - if (fd == -1) { + if (fd == -1) + { printf("open:: file not exist"); return false; } struct stat sb; - if (fstat(fd, &sb) == -1) { + if (fstat(fd, &sb) == -1) + { printf("open:: cannot open file"); return false; } @@ -71,21 +80,25 @@ bool PhraseReplacementMap::open(const char *path) length = (size_t)sb.st_size; data = mmap(NULL, length, PROT_READ, MAP_SHARED, fd, 0); - if (!data) { + if (!data) + { ::close(fd); return false; } - KeyValueBlobReader reader(static_cast(data), length); + KeyValueBlobReader reader(static_cast(data), length); KeyValueBlobReader::KeyValue keyValue; KeyValueBlobReader::State state; - while ((state = reader.Next(&keyValue)) == KeyValueBlobReader::State::HAS_PAIR) { + while ((state = reader.Next(&keyValue)) == KeyValueBlobReader::State::HAS_PAIR) + { keyValueMap[keyValue.key] = keyValue.value; } // 下面這一段或許可以做成開關、來詢問是否對使用者語彙採取寬鬆策略(哪怕有行內容寫錯也會放行) - if (state == KeyValueBlobReader::State::ERROR) { + if (state == KeyValueBlobReader::State::ERROR) + { // close(); - if (mgrPrefs.isDebugModeEnabled) syslog(LOG_CONS, "PhraseReplacementMap: Failed at Open Step 5. On Error Resume Next.\n"); + if (mgrPrefs.isDebugModeEnabled) + syslog(LOG_CONS, "PhraseReplacementMap: Failed at Open Step 5. On Error Resume Next.\n"); // return false; } return true; @@ -93,7 +106,8 @@ bool PhraseReplacementMap::open(const char *path) void PhraseReplacementMap::close() { - if (data) { + if (data) + { munmap(data, length); ::close(fd); data = 0; @@ -102,15 +116,15 @@ void PhraseReplacementMap::close() keyValueMap.clear(); } -const std::string PhraseReplacementMap::valueForKey(const std::string& key) +const std::string PhraseReplacementMap::valueForKey(const std::string &key) { auto iter = keyValueMap.find(key); - if (iter != keyValueMap.end()) { + if (iter != keyValueMap.end()) + { const std::string_view v = iter->second; return {v.data(), v.size()}; } return string(""); } - } diff --git a/Source/Modules/LangModelRelated/SubLanguageModels/UserOverrideModel.cpp b/Source/Modules/LangModelRelated/SubLanguageModels/UserOverrideModel.cpp index 8d30149a..4ae8443f 100644 --- a/Source/Modules/LangModelRelated/SubLanguageModels/UserOverrideModel.cpp +++ b/Source/Modules/LangModelRelated/SubLanguageModels/UserOverrideModel.cpp @@ -1,20 +1,27 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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. */ #include "UserOverrideModel.h" @@ -23,88 +30,84 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR TH #include #include -namespace vChewing { +namespace vChewing +{ // About 20 generations. static const double DecayThreshould = 1.0 / 1048576.0; -static double Score(size_t eventCount, - size_t totalCount, - double eventTimestamp, - double timestamp, - double lambda); -static bool IsEndingPunctuation(const std::string& value); -static std::string WalkedNodesToKey(const std::vector& walkedNodes, - size_t cursorIndex); +static double Score(size_t eventCount, size_t totalCount, double eventTimestamp, double timestamp, double lambda); +static bool IsEndingPunctuation(const std::string &value); +static std::string WalkedNodesToKey(const std::vector &walkedNodes, size_t cursorIndex); -UserOverrideModel::UserOverrideModel(size_t capacity, double decayConstant) - : m_capacity(capacity) { +UserOverrideModel::UserOverrideModel(size_t capacity, double decayConstant) : m_capacity(capacity) +{ assert(m_capacity > 0); m_decayExponent = log(0.5) / decayConstant; } -void UserOverrideModel::observe(const std::vector& walkedNodes, - size_t cursorIndex, - const std::string& candidate, - double timestamp) { +void UserOverrideModel::observe(const std::vector &walkedNodes, size_t cursorIndex, + const std::string &candidate, double timestamp) +{ std::string key = WalkedNodesToKey(walkedNodes, cursorIndex); auto mapIter = m_lruMap.find(key); - if (mapIter == m_lruMap.end()) { + if (mapIter == m_lruMap.end()) + { auto keyValuePair = KeyObservationPair(key, Observation()); - Observation& observation = keyValuePair.second; + Observation &observation = keyValuePair.second; observation.update(candidate, timestamp); m_lruList.push_front(keyValuePair); auto listIter = m_lruList.begin(); - auto lruKeyValue = std::pair::iterator>(key, listIter); + auto lruKeyValue = std::pair::iterator>(key, listIter); m_lruMap.insert(lruKeyValue); - if (m_lruList.size() > m_capacity) { + if (m_lruList.size() > m_capacity) + { auto lastKeyValuePair = m_lruList.end(); --lastKeyValuePair; m_lruMap.erase(lastKeyValuePair->first); m_lruList.pop_back(); } - } else { + } + else + { auto listIter = mapIter->second; m_lruList.splice(m_lruList.begin(), m_lruList, listIter); - auto& keyValuePair = *listIter; - Observation& observation = keyValuePair.second; + auto &keyValuePair = *listIter; + Observation &observation = keyValuePair.second; observation.update(candidate, timestamp); } } -std::string UserOverrideModel::suggest(const std::vector& walkedNodes, - size_t cursorIndex, - double timestamp) { +std::string UserOverrideModel::suggest(const std::vector &walkedNodes, size_t cursorIndex, + double timestamp) +{ std::string key = WalkedNodesToKey(walkedNodes, cursorIndex); auto mapIter = m_lruMap.find(key); - if (mapIter == m_lruMap.end()) { + if (mapIter == m_lruMap.end()) + { return std::string(); } auto listIter = mapIter->second; - auto& keyValuePair = *listIter; - const Observation& observation = keyValuePair.second; + auto &keyValuePair = *listIter; + const Observation &observation = keyValuePair.second; std::string candidate; double score = 0.0; - for (auto i = observation.overrides.begin(); - i != observation.overrides.end(); - ++i) { - const Override& o = i->second; - double overrideScore = Score(o.count, - observation.count, - o.timestamp, - timestamp, - m_decayExponent); - if (overrideScore == 0.0) { + for (auto i = observation.overrides.begin(); i != observation.overrides.end(); ++i) + { + const Override &o = i->second; + double overrideScore = Score(o.count, observation.count, o.timestamp, timestamp, m_decayExponent); + if (overrideScore == 0.0) + { continue; } - if (overrideScore > score) { + if (overrideScore > score) + { candidate = i->first; score = overrideScore; } @@ -112,21 +115,19 @@ std::string UserOverrideModel::suggest(const std::vector& walkedNodes, - size_t cursorIndex) { +static std::string WalkedNodesToKey(const std::vector &walkedNodes, size_t cursorIndex) +{ std::stringstream s; std::vector n; size_t ll = 0; - for (std::vector::const_iterator i = walkedNodes.begin(); - i != walkedNodes.end(); - ++i) { - const auto& nn = *i; + for (std::vector::const_iterator i = walkedNodes.begin(); i != walkedNodes.end(); ++i) + { + const auto &nn = *i; n.push_back(nn); ll += nn.spanningLength; - if (ll >= cursorIndex) { + if (ll >= cursorIndex) + { break; } } std::vector::const_reverse_iterator r = n.rbegin(); - if (r == n.rend()) { + if (r == n.rend()) + { return ""; } @@ -165,40 +168,44 @@ static std::string WalkedNodesToKey(const std::vector& s.clear(); s.str(std::string()); - if (r != n.rend()) { + if (r != n.rend()) + { std::string value = (*r).node->currentKeyValue().value; - if (IsEndingPunctuation(value)) { + if (IsEndingPunctuation(value)) + { s << "()"; r = n.rend(); - } else { - s << "(" - << (*r).node->currentKeyValue().key - << "," - << value - << ")"; + } + else + { + s << "(" << (*r).node->currentKeyValue().key << "," << value << ")"; ++r; } - } else { + } + else + { s << "()"; } std::string prev = s.str(); s.clear(); s.str(std::string()); - if (r != n.rend()) { + if (r != n.rend()) + { std::string value = (*r).node->currentKeyValue().value; - if (IsEndingPunctuation(value)) { + if (IsEndingPunctuation(value)) + { s << "()"; r = n.rend(); - } else { - s << "(" - << (*r).node->currentKeyValue().key - << "," - << value - << ")"; + } + else + { + s << "(" << (*r).node->currentKeyValue().key << "," << value << ")"; ++r; } - } else { + } + else + { s << "()"; } std::string anterior = s.str(); diff --git a/Source/Modules/LangModelRelated/SubLanguageModels/UserOverrideModel.h b/Source/Modules/LangModelRelated/SubLanguageModels/UserOverrideModel.h index 10824c4b..6479c2d6 100644 --- a/Source/Modules/LangModelRelated/SubLanguageModels/UserOverrideModel.h +++ b/Source/Modules/LangModelRelated/SubLanguageModels/UserOverrideModel.h @@ -1,20 +1,27 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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. */ #ifndef USEROVERRIDEMODEL_H @@ -25,37 +32,41 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR TH #include "Gramambular.h" -namespace vChewing { +namespace vChewing +{ using namespace Gramambular; -class UserOverrideModel { -public: +class UserOverrideModel +{ + public: UserOverrideModel(size_t capacity, double decayConstant); - void observe(const std::vector& walkedNodes, - size_t cursorIndex, - const std::string& candidate, - double timestamp); + void observe(const std::vector &walkedNodes, size_t cursorIndex, + const std::string &candidate, double timestamp); - std::string suggest(const std::vector& walkedNodes, - size_t cursorIndex, - double timestamp); + std::string suggest(const std::vector &walkedNodes, size_t cursorIndex, double timestamp); -private: - struct Override { + private: + struct Override + { size_t count; double timestamp; - Override() : count(0), timestamp(0.0) {} + Override() : count(0), timestamp(0.0) + { + } }; - struct Observation { + struct Observation + { size_t count; std::map overrides; - Observation() : count(0) {} - void update(const std::string& candidate, double timestamp); + Observation() : count(0) + { + } + void update(const std::string &candidate, double timestamp); }; typedef std::pair KeyObservationPair; @@ -69,4 +80,3 @@ private: }; // namespace vChewing #endif - diff --git a/Source/Modules/LangModelRelated/SubLanguageModels/UserPhrasesLM.h b/Source/Modules/LangModelRelated/SubLanguageModels/UserPhrasesLM.h index 05881911..4c27d748 100644 --- a/Source/Modules/LangModelRelated/SubLanguageModels/UserPhrasesLM.h +++ b/Source/Modules/LangModelRelated/SubLanguageModels/UserPhrasesLM.h @@ -1,35 +1,43 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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. */ #ifndef USERPHRASESLM_H #define USERPHRASESLM_H -#include -#include -#include #include "LanguageModel.h" +#include +#include +#include -namespace vChewing { +namespace vChewing +{ class UserPhrasesLM : public Gramambular::LanguageModel { -public: + public: UserPhrasesLM(); ~UserPhrasesLM(); @@ -38,31 +46,37 @@ public: void close(); void dump(); - virtual bool allowConsolidation() { + virtual bool allowConsolidation() + { return true; } - virtual float overridedValue() { + virtual float overridedValue() + { return 0.0; } - virtual const std::vector bigramsForKeys(const std::string& preceedingKey, const std::string& key); - virtual const std::vector unigramsForKey(const std::string& key); - virtual bool hasUnigramsForKey(const std::string& key); - -protected: - struct Row { - Row(std::string_view& k, std::string_view& v) : key(k), value(v) {} + virtual const std::vector bigramsForKeys(const std::string &preceedingKey, + const std::string &key); + virtual const std::vector unigramsForKey(const std::string &key); + virtual bool hasUnigramsForKey(const std::string &key); + + protected: + struct Row + { + Row(std::string_view &k, std::string_view &v) : key(k), value(v) + { + } std::string_view key; std::string_view value; }; - + std::map> keyRowMap; int fd; void *data; size_t length; }; -} +} // namespace vChewing #endif diff --git a/Source/Modules/LangModelRelated/SubLanguageModels/UserPhrasesLM.mm b/Source/Modules/LangModelRelated/SubLanguageModels/UserPhrasesLM.mm index b62db2db..e3565d0e 100644 --- a/Source/Modules/LangModelRelated/SubLanguageModels/UserPhrasesLM.mm +++ b/Source/Modules/LangModelRelated/SubLanguageModels/UserPhrasesLM.mm @@ -1,53 +1,60 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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. */ #include "UserPhrasesLM.h" #include "vChewing-Swift.h" -#include -#include #include #include -#include +#include +#include #include +#include #include "KeyValueBlobReader.h" #include "LMConsolidator.h" -namespace vChewing { +namespace vChewing +{ -UserPhrasesLM::UserPhrasesLM() - : fd(-1) - , data(0) - , length(0) +UserPhrasesLM::UserPhrasesLM() : fd(-1), data(0), length(0) { } UserPhrasesLM::~UserPhrasesLM() { - if (data) { + if (data) + { close(); } } bool UserPhrasesLM::isLoaded() { - if (data) { + if (data) + { return true; } return false; @@ -55,23 +62,27 @@ bool UserPhrasesLM::isLoaded() bool UserPhrasesLM::open(const char *path) { - if (data) { + if (data) + { return false; } - if (allowConsolidation()) { + if (allowConsolidation()) + { LMConsolidator::FixEOF(path); LMConsolidator::ConsolidateContent(path, true); } fd = ::open(path, O_RDONLY); - if (fd == -1) { + if (fd == -1) + { printf("open:: file not exist"); return false; } struct stat sb; - if (fstat(fd, &sb) == -1) { + if (fstat(fd, &sb) == -1) + { printf("open:: cannot open file"); return false; } @@ -79,22 +90,27 @@ bool UserPhrasesLM::open(const char *path) length = (size_t)sb.st_size; data = mmap(NULL, length, PROT_READ, MAP_SHARED, fd, 0); - if (!data) { + if (!data) + { ::close(fd); return false; } - KeyValueBlobReader reader(static_cast(data), length); + KeyValueBlobReader reader(static_cast(data), length); KeyValueBlobReader::KeyValue keyValue; KeyValueBlobReader::State state; - while ((state = reader.Next(&keyValue)) == KeyValueBlobReader::State::HAS_PAIR) { - // We invert the key and value, since in user phrases, "key" is the phrase value, and "value" is the BPMF reading. + while ((state = reader.Next(&keyValue)) == KeyValueBlobReader::State::HAS_PAIR) + { + // We invert the key and value, since in user phrases, "key" is the phrase value, and "value" is the BPMF + // reading. keyRowMap[keyValue.value].emplace_back(keyValue.value, keyValue.key); } // 下面這一段或許可以做成開關、來詢問是否對使用者語彙採取寬鬆策略(哪怕有行內容寫錯也會放行) - if (state == KeyValueBlobReader::State::ERROR) { + if (state == KeyValueBlobReader::State::ERROR) + { // close(); - if (mgrPrefs.isDebugModeEnabled) syslog(LOG_CONS, "UserPhrasesLM: Failed at Open Step 5. On Error Resume Next.\n"); + if (mgrPrefs.isDebugModeEnabled) + syslog(LOG_CONS, "UserPhrasesLM: Failed at Open Step 5. On Error Resume Next.\n"); // return false; } return true; @@ -102,7 +118,8 @@ bool UserPhrasesLM::open(const char *path) void UserPhrasesLM::close() { - if (data) { + if (data) + { munmap(data, length); ::close(fd); data = 0; @@ -113,26 +130,31 @@ void UserPhrasesLM::close() void UserPhrasesLM::dump() { - for (const auto& entry : keyRowMap) { - const std::vector& rows = entry.second; - for (const auto& row : rows) { + for (const auto &entry : keyRowMap) + { + const std::vector &rows = entry.second; + for (const auto &row : rows) + { std::cerr << row.key << " " << row.value << "\n"; } } } -const std::vector UserPhrasesLM::bigramsForKeys(const std::string& preceedingKey, const std::string& key) +const std::vector UserPhrasesLM::bigramsForKeys(const std::string &preceedingKey, + const std::string &key) { return std::vector(); } -const std::vector UserPhrasesLM::unigramsForKey(const std::string& key) +const std::vector UserPhrasesLM::unigramsForKey(const std::string &key) { std::vector v; auto iter = keyRowMap.find(key); - if (iter != keyRowMap.end()) { - const std::vector& rows = iter->second; - for (const auto& row : rows) { + if (iter != keyRowMap.end()) + { + const std::vector &rows = iter->second; + for (const auto &row : rows) + { Gramambular::Unigram g; g.keyValue.key = row.key; g.keyValue.value = row.value; @@ -144,9 +166,9 @@ const std::vector UserPhrasesLM::unigramsForKey(const std: return v; } -bool UserPhrasesLM::hasUnigramsForKey(const std::string& key) +bool UserPhrasesLM::hasUnigramsForKey(const std::string &key) { return keyRowMap.find(key) != keyRowMap.end(); } -}; // namespace vChewing +}; // namespace vChewing diff --git a/Source/Modules/LangModelRelated/mgrLangModel.h b/Source/Modules/LangModelRelated/mgrLangModel.h index 0a3d3332..816d44cb 100644 --- a/Source/Modules/LangModelRelated/mgrLangModel.h +++ b/Source/Modules/LangModelRelated/mgrLangModel.h @@ -1,24 +1,31 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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 #import "KeyHandler.h" +#import NS_ASSUME_NONNULL_BEGIN @@ -33,8 +40,13 @@ NS_ASSUME_NONNULL_BEGIN + (BOOL)checkIfSpecifiedUserDataFolderValid:(NSString *)folderPath; + (NSString *)dataFolderPath:(bool)isDefaultFolder NS_SWIFT_NAME(dataFolderPath(isDefaultFolder:)); -+ (BOOL)checkIfUserPhraseExist:(NSString *)userPhrase inputMode:(InputMode)mode key:(NSString *)key NS_SWIFT_NAME(checkIfUserPhraseExist(userPhrase:mode:key:)); -+ (BOOL)writeUserPhrase:(NSString *)userPhrase inputMode:(InputMode)mode areWeDuplicating:(BOOL)areWeDuplicating areWeDeleting:(BOOL)areWeDeleting; ++ (BOOL)checkIfUserPhraseExist:(NSString *)userPhrase + inputMode:(InputMode)mode + key:(NSString *)key NS_SWIFT_NAME(checkIfUserPhraseExist(userPhrase:mode:key:)); ++ (BOOL)writeUserPhrase:(NSString *)userPhrase + inputMode:(InputMode)mode + areWeDuplicating:(BOOL)areWeDuplicating + areWeDeleting:(BOOL)areWeDeleting; + (void)setPhraseReplacementEnabled:(BOOL)phraseReplacementEnabled; + (void)setCNSEnabled:(BOOL)cnsEnabled; + (void)setSymbolEnabled:(BOOL)symbolEnabled; diff --git a/Source/Modules/LangModelRelated/mgrLangModel.mm b/Source/Modules/LangModelRelated/mgrLangModel.mm index 3ca207ba..9b055161 100644 --- a/Source/Modules/LangModelRelated/mgrLangModel.mm +++ b/Source/Modules/LangModelRelated/mgrLangModel.mm @@ -1,26 +1,33 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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 "mgrLangModel.h" +#import "LMConsolidator.h" #import "mgrLangModel_Privates.h" #import "vChewing-Swift.h" -#import "LMConsolidator.h" static const int kUserOverrideModelCapacity = 500; static const double kObservedOverrideHalflife = 5400.0; @@ -54,70 +61,90 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing + (void)loadDataModels { - if (!gLangModelCHT.isDataModelLoaded()) { + if (!gLangModelCHT.isDataModelLoaded()) + { LTLoadLanguageModelFile(@"data-cht", gLangModelCHT); } - if (!gLangModelCHT.isMiscDataLoaded()) { - gLangModelCHT.loadMiscData([[self specifyBundleDataPath: @"data-zhuyinwen"] UTF8String]); + if (!gLangModelCHT.isMiscDataLoaded()) + { + gLangModelCHT.loadMiscData([[self specifyBundleDataPath:@"data-zhuyinwen"] UTF8String]); } - if (!gLangModelCHT.isSymbolDataLoaded()){ - gLangModelCHT.loadSymbolData([[self specifyBundleDataPath: @"data-symbols"] UTF8String]); + if (!gLangModelCHT.isSymbolDataLoaded()) + { + gLangModelCHT.loadSymbolData([[self specifyBundleDataPath:@"data-symbols"] UTF8String]); } - if (!gLangModelCHT.isCNSDataLoaded()){ - gLangModelCHT.loadCNSData([[self specifyBundleDataPath: @"char-kanji-cns"] UTF8String]); + if (!gLangModelCHT.isCNSDataLoaded()) + { + gLangModelCHT.loadCNSData([[self specifyBundleDataPath:@"char-kanji-cns"] UTF8String]); } // ----------------- - if (!gLangModelCHS.isDataModelLoaded()) { + if (!gLangModelCHS.isDataModelLoaded()) + { LTLoadLanguageModelFile(@"data-chs", gLangModelCHS); } - if (!gLangModelCHS.isMiscDataLoaded()) { - gLangModelCHS.loadMiscData([[self specifyBundleDataPath: @"data-zhuyinwen"] UTF8String]); + if (!gLangModelCHS.isMiscDataLoaded()) + { + gLangModelCHS.loadMiscData([[self specifyBundleDataPath:@"data-zhuyinwen"] UTF8String]); } - if (!gLangModelCHS.isSymbolDataLoaded()){ - gLangModelCHS.loadSymbolData([[self specifyBundleDataPath: @"data-symbols"] UTF8String]); + if (!gLangModelCHS.isSymbolDataLoaded()) + { + gLangModelCHS.loadSymbolData([[self specifyBundleDataPath:@"data-symbols"] UTF8String]); } - if (!gLangModelCHS.isCNSDataLoaded()){ - gLangModelCHS.loadCNSData([[self specifyBundleDataPath: @"char-kanji-cns"] UTF8String]); + if (!gLangModelCHS.isCNSDataLoaded()) + { + gLangModelCHS.loadCNSData([[self specifyBundleDataPath:@"char-kanji-cns"] UTF8String]); } } + (void)loadDataModel:(InputMode)mode { - if ([mode isEqualToString:imeModeCHT]) { - if (!gLangModelCHT.isDataModelLoaded()) { + if ([mode isEqualToString:imeModeCHT]) + { + if (!gLangModelCHT.isDataModelLoaded()) + { LTLoadLanguageModelFile(@"data-cht", gLangModelCHT); } - if (!gLangModelCHT.isMiscDataLoaded()) { - gLangModelCHT.loadMiscData([[self specifyBundleDataPath: @"data-zhuyinwen"] UTF8String]); + if (!gLangModelCHT.isMiscDataLoaded()) + { + gLangModelCHT.loadMiscData([[self specifyBundleDataPath:@"data-zhuyinwen"] UTF8String]); } - if (!gLangModelCHT.isSymbolDataLoaded()){ - gLangModelCHT.loadSymbolData([[self specifyBundleDataPath: @"data-symbols"] UTF8String]); + if (!gLangModelCHT.isSymbolDataLoaded()) + { + gLangModelCHT.loadSymbolData([[self specifyBundleDataPath:@"data-symbols"] UTF8String]); } - if (!gLangModelCHT.isCNSDataLoaded()){ - gLangModelCHT.loadCNSData([[self specifyBundleDataPath: @"char-kanji-cns"] UTF8String]); + if (!gLangModelCHT.isCNSDataLoaded()) + { + gLangModelCHT.loadCNSData([[self specifyBundleDataPath:@"char-kanji-cns"] UTF8String]); } } - if ([mode isEqualToString:imeModeCHS]) { - if (!gLangModelCHS.isDataModelLoaded()) { + if ([mode isEqualToString:imeModeCHS]) + { + if (!gLangModelCHS.isDataModelLoaded()) + { LTLoadLanguageModelFile(@"data-chs", gLangModelCHS); } - if (!gLangModelCHS.isMiscDataLoaded()) { - gLangModelCHS.loadMiscData([[self specifyBundleDataPath: @"data-zhuyinwen"] UTF8String]); + if (!gLangModelCHS.isMiscDataLoaded()) + { + gLangModelCHS.loadMiscData([[self specifyBundleDataPath:@"data-zhuyinwen"] UTF8String]); } - if (!gLangModelCHS.isSymbolDataLoaded()){ - gLangModelCHS.loadSymbolData([[self specifyBundleDataPath: @"data-symbols"] UTF8String]); + if (!gLangModelCHS.isSymbolDataLoaded()) + { + gLangModelCHS.loadSymbolData([[self specifyBundleDataPath:@"data-symbols"] UTF8String]); } - if (!gLangModelCHS.isCNSDataLoaded()){ - gLangModelCHS.loadCNSData([[self specifyBundleDataPath: @"char-kanji-cns"] UTF8String]); + if (!gLangModelCHS.isCNSDataLoaded()) + { + gLangModelCHS.loadCNSData([[self specifyBundleDataPath:@"char-kanji-cns"] UTF8String]); } } } + (void)loadUserPhrases { - gLangModelCHT.loadUserPhrases([[self userPhrasesDataPath:imeModeCHT] UTF8String], [[self excludedPhrasesDataPath:imeModeCHT] UTF8String]); - gLangModelCHS.loadUserPhrases([[self userPhrasesDataPath:imeModeCHS] UTF8String], [[self excludedPhrasesDataPath:imeModeCHS] UTF8String]); + gLangModelCHT.loadUserPhrases([[self userPhrasesDataPath:imeModeCHT] UTF8String], + [[self excludedPhrasesDataPath:imeModeCHT] UTF8String]); + gLangModelCHS.loadUserPhrases([[self userPhrasesDataPath:imeModeCHS] UTF8String], + [[self excludedPhrasesDataPath:imeModeCHS] UTF8String]); gLangModelCHT.loadUserSymbolData([[self userSymbolDataPath:imeModeCHT] UTF8String]); gLangModelCHS.loadUserSymbolData([[self userSymbolDataPath:imeModeCHS] UTF8String]); } @@ -139,19 +166,26 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing NSString *folderPath = [self dataFolderPath:false]; BOOL isFolder = NO; BOOL folderExist = [[NSFileManager defaultManager] fileExistsAtPath:folderPath isDirectory:&isFolder]; - if (folderExist && !isFolder) { + if (folderExist && !isFolder) + { NSError *error = nil; [[NSFileManager defaultManager] removeItemAtPath:folderPath error:&error]; - if (error) { + if (error) + { NSLog(@"Failed to remove folder %@", error); return NO; } folderExist = NO; } - if (!folderExist) { + if (!folderExist) + { NSError *error = nil; - [[NSFileManager defaultManager] createDirectoryAtPath:folderPath withIntermediateDirectories:YES attributes:nil error:&error]; - if (error) { + [[NSFileManager defaultManager] createDirectoryAtPath:folderPath + withIntermediateDirectories:YES + attributes:nil + error:&error]; + if (error) + { NSLog(@"Failed to create folder %@", error); return NO; } @@ -163,26 +197,34 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing { BOOL isFolder = NO; BOOL folderExist = [[NSFileManager defaultManager] fileExistsAtPath:folderPath isDirectory:&isFolder]; - if ((folderExist && !isFolder) || (!folderExist)) { + if ((folderExist && !isFolder) || (!folderExist)) + { return NO; } return YES; } -+ (BOOL)ensureFileExists:(NSString *)filePath populateWithTemplate:(NSString *)templateBasename extension:(NSString *)ext ++ (BOOL)ensureFileExists:(NSString *)filePath + populateWithTemplate:(NSString *)templateBasename + extension:(NSString *)ext { - if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) { + if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) + { NSURL *templateURL = [[NSBundle mainBundle] URLForResource:templateBasename withExtension:ext]; NSData *templateData; - if (templateURL) { + if (templateURL) + { templateData = [NSData dataWithContentsOfURL:templateURL]; - } else { + } + else + { templateData = [@"" dataUsingEncoding:NSUTF8StringEncoding]; } BOOL result = [templateData writeToFile:filePath atomically:YES]; - if (!result) { + if (!result) + { NSLog(@"Failed to write file"); return NO; } @@ -192,36 +234,76 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing + (BOOL)checkIfUserLanguageModelFilesExist { - if (![self checkIfUserDataFolderExists]) return NO; - if (![self ensureFileExists:[self userPhrasesDataPath:imeModeCHS] populateWithTemplate:kUserDataTemplateName extension:kTemplateExtension]) return NO; - if (![self ensureFileExists:[self userPhrasesDataPath:imeModeCHT] populateWithTemplate:kUserDataTemplateName extension:kTemplateExtension]) return NO; - if (![self ensureFileExists:[self userAssociatedPhrasesDataPath:imeModeCHS] populateWithTemplate:kUserAssDataTemplateName extension:kTemplateExtension]) return NO; - if (![self ensureFileExists:[self userAssociatedPhrasesDataPath:imeModeCHT] populateWithTemplate:kUserAssDataTemplateName extension:kTemplateExtension]) return NO; - if (![self ensureFileExists:[self excludedPhrasesDataPath:imeModeCHS] populateWithTemplate:kExcludedPhrasesvChewingTemplateName extension:kTemplateExtension]) return NO; - if (![self ensureFileExists:[self excludedPhrasesDataPath:imeModeCHT] populateWithTemplate:kExcludedPhrasesvChewingTemplateName extension:kTemplateExtension]) return NO; - if (![self ensureFileExists:[self phraseReplacementDataPath:imeModeCHS] populateWithTemplate:kPhraseReplacementTemplateName extension:kTemplateExtension]) return NO; - if (![self ensureFileExists:[self phraseReplacementDataPath:imeModeCHT] populateWithTemplate:kPhraseReplacementTemplateName extension:kTemplateExtension]) return NO; - if (![self ensureFileExists:[self userSymbolDataPath:imeModeCHT] populateWithTemplate:kUserSymbolDataTemplateName extension:kTemplateExtension]) return NO; - if (![self ensureFileExists:[self userSymbolDataPath:imeModeCHS] populateWithTemplate:kUserSymbolDataTemplateName extension:kTemplateExtension]) return NO; + if (![self checkIfUserDataFolderExists]) + return NO; + if (![self ensureFileExists:[self userPhrasesDataPath:imeModeCHS] + populateWithTemplate:kUserDataTemplateName + extension:kTemplateExtension]) + return NO; + if (![self ensureFileExists:[self userPhrasesDataPath:imeModeCHT] + populateWithTemplate:kUserDataTemplateName + extension:kTemplateExtension]) + return NO; + if (![self ensureFileExists:[self userAssociatedPhrasesDataPath:imeModeCHS] + populateWithTemplate:kUserAssDataTemplateName + extension:kTemplateExtension]) + return NO; + if (![self ensureFileExists:[self userAssociatedPhrasesDataPath:imeModeCHT] + populateWithTemplate:kUserAssDataTemplateName + extension:kTemplateExtension]) + return NO; + if (![self ensureFileExists:[self excludedPhrasesDataPath:imeModeCHS] + populateWithTemplate:kExcludedPhrasesvChewingTemplateName + extension:kTemplateExtension]) + return NO; + if (![self ensureFileExists:[self excludedPhrasesDataPath:imeModeCHT] + populateWithTemplate:kExcludedPhrasesvChewingTemplateName + extension:kTemplateExtension]) + return NO; + if (![self ensureFileExists:[self phraseReplacementDataPath:imeModeCHS] + populateWithTemplate:kPhraseReplacementTemplateName + extension:kTemplateExtension]) + return NO; + if (![self ensureFileExists:[self phraseReplacementDataPath:imeModeCHT] + populateWithTemplate:kPhraseReplacementTemplateName + extension:kTemplateExtension]) + return NO; + if (![self ensureFileExists:[self userSymbolDataPath:imeModeCHT] + populateWithTemplate:kUserSymbolDataTemplateName + extension:kTemplateExtension]) + return NO; + if (![self ensureFileExists:[self userSymbolDataPath:imeModeCHS] + populateWithTemplate:kUserSymbolDataTemplateName + extension:kTemplateExtension]) + return NO; return YES; } -+ (BOOL)checkIfUserPhraseExist:(NSString *)userPhrase inputMode:(InputMode)mode key:(NSString *)key NS_SWIFT_NAME(checkIfUserPhraseExist(userPhrase:mode:key:)) ++ (BOOL)checkIfUserPhraseExist:(NSString *)userPhrase + inputMode:(InputMode)mode + key:(NSString *)key NS_SWIFT_NAME(checkIfUserPhraseExist(userPhrase:mode:key:)) { string unigramKey = string(key.UTF8String); - vector unigrams = [mode isEqualToString:imeModeCHT] ? gLangModelCHT.unigramsForKey(unigramKey): gLangModelCHS.unigramsForKey(unigramKey); + vector unigrams = [mode isEqualToString:imeModeCHT] ? gLangModelCHT.unigramsForKey(unigramKey) + : gLangModelCHS.unigramsForKey(unigramKey); string userPhraseString = string(userPhrase.UTF8String); - for (auto unigram: unigrams) { - if (unigram.keyValue.value == userPhraseString) { + for (auto unigram : unigrams) + { + if (unigram.keyValue.value == userPhraseString) + { return YES; } } return NO; } -+ (BOOL)writeUserPhrase:(NSString *)userPhrase inputMode:(InputMode)mode areWeDuplicating:(BOOL)areWeDuplicating areWeDeleting:(BOOL)areWeDeleting ++ (BOOL)writeUserPhrase:(NSString *)userPhrase + inputMode:(InputMode)mode + areWeDuplicating:(BOOL)areWeDuplicating + areWeDeleting:(BOOL)areWeDeleting { - if (![self checkIfUserLanguageModelFilesExist]) { + if (![self checkIfUserLanguageModelFilesExist]) + { return NO; } @@ -233,7 +315,8 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing // [currentMarkedPhrase appendString:@"\n"]; // } [currentMarkedPhrase appendString:userPhrase]; - if (areWeDuplicating && !areWeDeleting) { + if (areWeDuplicating && !areWeDeleting) + { // Do not use ASCII characters to comment here. // Otherwise, it will be scrambled by cnvHYPYtoBPMF module shipped in the vChewing Phrase Editor. [currentMarkedPhrase appendString:@"\t#𝙾𝚟𝚎𝚛𝚛𝚒𝚍𝚎"]; @@ -241,7 +324,8 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing [currentMarkedPhrase appendString:@"\n"]; NSFileHandle *writeFile = [NSFileHandle fileHandleForUpdatingAtPath:path]; - if (!writeFile) { + if (!writeFile) + { return NO; } [writeFile seekToEndOfFile]; @@ -249,12 +333,14 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing [writeFile writeData:data]; [writeFile closeFile]; - // We enforce the format consolidation here, since the pragma header will let the UserPhraseLM bypasses the consolidating process on load. + // We enforce the format consolidation here, since the pragma header will let the UserPhraseLM bypasses the + // consolidating process on load. vChewing::LMConsolidator::ConsolidateContent([path UTF8String], false); // We use FSEventStream to monitor the change of the user phrase folder, // so we don't have to load data here unless FSEventStream is disabled by user. - if (!mgrPrefs.shouldAutoReloadUserDataFiles) { + if (!mgrPrefs.shouldAutoReloadUserDataFiles) + { [self loadUserPhrases]; } return YES; @@ -263,15 +349,21 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing + (NSString *)dataFolderPath:(bool)isDefaultFolder { // 此處不能用「~」來取代當前使用者目錄名稱。不然的話,一旦輸入法被系統的沙箱干預的話,則反而會定位到沙箱目錄內。 - NSString *appSupportPath = [NSFileManager.defaultManager URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask][0].path; + NSString *appSupportPath = [NSFileManager.defaultManager URLsForDirectory:NSApplicationSupportDirectory + inDomains:NSUserDomainMask][0].path; NSString *userDictPath = [appSupportPath stringByAppendingPathComponent:@"vChewing"].stringByExpandingTildeInPath; - if (mgrPrefs.userDataFolderSpecified.stringByExpandingTildeInPath == userDictPath || isDefaultFolder) { + if (mgrPrefs.userDataFolderSpecified.stringByExpandingTildeInPath == userDictPath || isDefaultFolder) + { return userDictPath; } - if ([mgrPrefs ifSpecifiedUserDataPathExistsInPlist]) { - if ([self checkIfSpecifiedUserDataFolderValid:mgrPrefs.userDataFolderSpecified.stringByExpandingTildeInPath]) { + if ([mgrPrefs ifSpecifiedUserDataPathExistsInPlist]) + { + if ([self checkIfSpecifiedUserDataFolderValid:mgrPrefs.userDataFolderSpecified.stringByExpandingTildeInPath]) + { return mgrPrefs.userDataFolderSpecified.stringByExpandingTildeInPath; - } else { + } + else + { [NSUserDefaults.standardUserDefaults removeObjectForKey:@"UserDataFolderSpecified"]; } } @@ -286,13 +378,15 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing + (NSString *)userSymbolDataPath:(InputMode)mode; { - NSString *fileName = [mode isEqualToString:imeModeCHT] ? @"usersymbolphrases-cht.txt" : @"usersymbolphrases-chs.txt"; + NSString *fileName = + [mode isEqualToString:imeModeCHT] ? @"usersymbolphrases-cht.txt" : @"usersymbolphrases-chs.txt"; return [[self dataFolderPath:false] stringByAppendingPathComponent:fileName]; } + (NSString *)userAssociatedPhrasesDataPath:(InputMode)mode; { - NSString *fileName = [mode isEqualToString:imeModeCHT] ? @"associatedPhrases-cht.txt" : @"associatedPhrases-chs.txt"; + NSString *fileName = + [mode isEqualToString:imeModeCHT] ? @"associatedPhrases-cht.txt" : @"associatedPhrases-chs.txt"; return [[self dataFolderPath:false] stringByAppendingPathComponent:fileName]; } @@ -304,11 +398,12 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing + (NSString *)phraseReplacementDataPath:(InputMode)mode; { - NSString *fileName = [mode isEqualToString:imeModeCHT] ? @"phrases-replacement-cht.txt" : @"phrases-replacement-chs.txt"; + NSString *fileName = + [mode isEqualToString:imeModeCHT] ? @"phrases-replacement-cht.txt" : @"phrases-replacement-chs.txt"; return [[self dataFolderPath:false] stringByAppendingPathComponent:fileName]; } - + (vChewing::LMInstantiator *)lmCHT ++ (vChewing::LMInstantiator *)lmCHT { return &gLangModelCHT; } diff --git a/Source/Modules/LangModelRelated/mgrLangModel_Privates.h b/Source/Modules/LangModelRelated/mgrLangModel_Privates.h index 22077d25..cc42ca2a 100644 --- a/Source/Modules/LangModelRelated/mgrLangModel_Privates.h +++ b/Source/Modules/LangModelRelated/mgrLangModel_Privates.h @@ -1,33 +1,40 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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 "mgrLangModel.h" -#import "UserOverrideModel.h" #import "LMInstantiator.h" +#import "UserOverrideModel.h" +#import "mgrLangModel.h" NS_ASSUME_NONNULL_BEGIN @interface mgrLangModel () -@property (class, readonly, nonatomic) vChewing::LMInstantiator *lmCHT; -@property (class, readonly, nonatomic) vChewing::LMInstantiator *lmCHS; -@property (class, readonly, nonatomic) vChewing::UserOverrideModel *userOverrideModelCHS; -@property (class, readonly, nonatomic) vChewing::UserOverrideModel *userOverrideModelCHT; +@property(class, readonly, nonatomic) vChewing::LMInstantiator *lmCHT; +@property(class, readonly, nonatomic) vChewing::LMInstantiator *lmCHS; +@property(class, readonly, nonatomic) vChewing::UserOverrideModel *userOverrideModelCHS; +@property(class, readonly, nonatomic) vChewing::UserOverrideModel *userOverrideModelCHT; @end NS_ASSUME_NONNULL_END diff --git a/Source/Modules/LanguageParsers/Gramambular/Bigram.h b/Source/Modules/LanguageParsers/Gramambular/Bigram.h index 16576b31..a4b8c8b2 100644 --- a/Source/Modules/LanguageParsers/Gramambular/Bigram.h +++ b/Source/Modules/LanguageParsers/Gramambular/Bigram.h @@ -1,20 +1,27 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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. */ #ifndef BIGRAM_H_ @@ -24,69 +31,80 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR TH #include "KeyValuePair.h" -namespace Gramambular { -class Bigram { -public: +namespace Gramambular +{ +class Bigram +{ + public: Bigram(); - + KeyValuePair preceedingKeyValue; KeyValuePair keyValue; double score; - - bool operator==(const Bigram& another) const; - bool operator<(const Bigram& another) const; + + bool operator==(const Bigram &another) const; + bool operator<(const Bigram &another) const; }; -inline std::ostream& operator<<(std::ostream& stream, const Bigram& gram) { +inline std::ostream &operator<<(std::ostream &stream, const Bigram &gram) +{ std::streamsize p = stream.precision(); stream.precision(6); - stream << "(" << gram.keyValue << "|" << gram.preceedingKeyValue << "," - << gram.score << ")"; + stream << "(" << gram.keyValue << "|" << gram.preceedingKeyValue << "," << gram.score << ")"; stream.precision(p); return stream; } -inline std::ostream& operator<<(std::ostream& stream, - const std::vector& grams) { +inline std::ostream &operator<<(std::ostream &stream, const std::vector &grams) +{ stream << "[" << grams.size() << "]=>{"; - + size_t index = 0; - - for (std::vector::const_iterator gi = grams.begin(); - gi != grams.end(); ++gi, ++index) { + + for (std::vector::const_iterator gi = grams.begin(); gi != grams.end(); ++gi, ++index) + { stream << index << "=>"; stream << *gi; - if (gi + 1 != grams.end()) { + if (gi + 1 != grams.end()) + { stream << ","; } } - + stream << "}"; return stream; } -inline Bigram::Bigram() : score(0.0) {} - -inline bool Bigram::operator==(const Bigram& another) const { - return preceedingKeyValue == another.preceedingKeyValue && - keyValue == another.keyValue && score == another.score; +inline Bigram::Bigram() : score(0.0) +{ } -inline bool Bigram::operator<(const Bigram& another) const { - if (preceedingKeyValue < another.preceedingKeyValue) { +inline bool Bigram::operator==(const Bigram &another) const +{ + return preceedingKeyValue == another.preceedingKeyValue && keyValue == another.keyValue && score == another.score; +} + +inline bool Bigram::operator<(const Bigram &another) const +{ + if (preceedingKeyValue < another.preceedingKeyValue) + { return true; - } else if (preceedingKeyValue == another.preceedingKeyValue) { - if (keyValue < another.keyValue) { + } + else if (preceedingKeyValue == another.preceedingKeyValue) + { + if (keyValue < another.keyValue) + { return true; - } else if (keyValue == another.keyValue) { + } + else if (keyValue == another.keyValue) + { return score < another.score; } return false; } - + return false; } -} // namespace Gramambular - +} // namespace Gramambular #endif diff --git a/Source/Modules/LanguageParsers/Gramambular/BlockReadingBuilder.h b/Source/Modules/LanguageParsers/Gramambular/BlockReadingBuilder.h index 4ece863b..12046b15 100644 --- a/Source/Modules/LanguageParsers/Gramambular/BlockReadingBuilder.h +++ b/Source/Modules/LanguageParsers/Gramambular/BlockReadingBuilder.h @@ -1,20 +1,27 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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. */ #ifndef BLOCKREADINGBUILDER_H_ @@ -26,157 +33,186 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR TH #include "Grid.h" #include "LanguageModel.h" -namespace Gramambular { +namespace Gramambular +{ -class BlockReadingBuilder { -public: - explicit BlockReadingBuilder(LanguageModel* lm); +class BlockReadingBuilder +{ + public: + explicit BlockReadingBuilder(LanguageModel *lm); void clear(); - + size_t length() const; size_t cursorIndex() const; void setCursorIndex(size_t newIndex); - void insertReadingAtCursor(const std::string& reading); - bool deleteReadingBeforeCursor(); // backspace - bool deleteReadingAfterCursor(); // delete - + void insertReadingAtCursor(const std::string &reading); + bool deleteReadingBeforeCursor(); // backspace + bool deleteReadingAfterCursor(); // delete + bool removeHeadReadings(size_t count); - - void setJoinSeparator(const std::string& separator); + + void setJoinSeparator(const std::string &separator); const std::string joinSeparator() const; - + std::vector readings() const; - - Grid& grid(); - -protected: + + Grid &grid(); + + protected: void build(); - + static const std::string Join(std::vector::const_iterator begin, - std::vector::const_iterator end, - const std::string& separator); - + std::vector::const_iterator end, const std::string &separator); + // 規定最多可以組成的詞的字數上限為 10 static const size_t MaximumBuildSpanLength = 10; - + size_t m_cursorIndex; std::vector m_readings; - + Grid m_grid; - LanguageModel* m_LM; + LanguageModel *m_LM; std::string m_joinSeparator; }; -inline BlockReadingBuilder::BlockReadingBuilder(LanguageModel* lm) -: m_LM(lm), m_cursorIndex(0) {} +inline BlockReadingBuilder::BlockReadingBuilder(LanguageModel *lm) : m_LM(lm), m_cursorIndex(0) +{ +} -inline void BlockReadingBuilder::clear() { +inline void BlockReadingBuilder::clear() +{ m_cursorIndex = 0; m_readings.clear(); m_grid.clear(); } -inline size_t BlockReadingBuilder::length() const { return m_readings.size(); } +inline size_t BlockReadingBuilder::length() const +{ + return m_readings.size(); +} -inline size_t BlockReadingBuilder::cursorIndex() const { return m_cursorIndex; } +inline size_t BlockReadingBuilder::cursorIndex() const +{ + return m_cursorIndex; +} -inline void BlockReadingBuilder::setCursorIndex(size_t newIndex) { +inline void BlockReadingBuilder::setCursorIndex(size_t newIndex) +{ m_cursorIndex = newIndex > m_readings.size() ? m_readings.size() : newIndex; } -inline void BlockReadingBuilder::insertReadingAtCursor( - const std::string& reading) { +inline void BlockReadingBuilder::insertReadingAtCursor(const std::string &reading) +{ m_readings.insert(m_readings.begin() + m_cursorIndex, reading); - + m_grid.expandGridByOneAtLocation(m_cursorIndex); build(); m_cursorIndex++; } -inline std::vector BlockReadingBuilder::readings() const { +inline std::vector BlockReadingBuilder::readings() const +{ return m_readings; } -inline bool BlockReadingBuilder::deleteReadingBeforeCursor() { - if (!m_cursorIndex) { +inline bool BlockReadingBuilder::deleteReadingBeforeCursor() +{ + if (!m_cursorIndex) + { return false; } - - m_readings.erase(m_readings.begin() + m_cursorIndex - 1, - m_readings.begin() + m_cursorIndex); + + m_readings.erase(m_readings.begin() + m_cursorIndex - 1, m_readings.begin() + m_cursorIndex); m_cursorIndex--; m_grid.shrinkGridByOneAtLocation(m_cursorIndex); build(); return true; } -inline bool BlockReadingBuilder::deleteReadingAfterCursor() { - if (m_cursorIndex == m_readings.size()) { +inline bool BlockReadingBuilder::deleteReadingAfterCursor() +{ + if (m_cursorIndex == m_readings.size()) + { return false; } - - m_readings.erase(m_readings.begin() + m_cursorIndex, - m_readings.begin() + m_cursorIndex + 1); + + m_readings.erase(m_readings.begin() + m_cursorIndex, m_readings.begin() + m_cursorIndex + 1); m_grid.shrinkGridByOneAtLocation(m_cursorIndex); build(); return true; } -inline bool BlockReadingBuilder::removeHeadReadings(size_t count) { - if (count > length()) { +inline bool BlockReadingBuilder::removeHeadReadings(size_t count) +{ + if (count > length()) + { return false; } - - for (size_t i = 0; i < count; i++) { - if (m_cursorIndex) { + + for (size_t i = 0; i < count; i++) + { + if (m_cursorIndex) + { m_cursorIndex--; } m_readings.erase(m_readings.begin(), m_readings.begin() + 1); m_grid.shrinkGridByOneAtLocation(0); build(); } - + return true; } -inline void BlockReadingBuilder::setJoinSeparator( - const std::string& separator) { +inline void BlockReadingBuilder::setJoinSeparator(const std::string &separator) +{ m_joinSeparator = separator; } -inline const std::string BlockReadingBuilder::joinSeparator() const { +inline const std::string BlockReadingBuilder::joinSeparator() const +{ return m_joinSeparator; } -inline Grid& BlockReadingBuilder::grid() { return m_grid; } +inline Grid &BlockReadingBuilder::grid() +{ + return m_grid; +} -inline void BlockReadingBuilder::build() { - if (!m_LM) { +inline void BlockReadingBuilder::build() +{ + if (!m_LM) + { return; } - + size_t begin = 0; size_t end = m_cursorIndex + MaximumBuildSpanLength; - - if (m_cursorIndex < MaximumBuildSpanLength) { + + if (m_cursorIndex < MaximumBuildSpanLength) + { begin = 0; - } else { + } + else + { begin = m_cursorIndex - MaximumBuildSpanLength; } - - if (end > m_readings.size()) { + + if (end > m_readings.size()) + { end = m_readings.size(); } - - for (size_t p = begin; p < end; p++) { - for (size_t q = 1; q <= MaximumBuildSpanLength && p + q <= end; q++) { - std::string combinedReading = Join( - m_readings.begin() + p, m_readings.begin() + p + q, m_joinSeparator); - if (!m_grid.hasNodeAtLocationSpanningLengthMatchingKey(p, q, - combinedReading)) { + + for (size_t p = begin; p < end; p++) + { + for (size_t q = 1; q <= MaximumBuildSpanLength && p + q <= end; q++) + { + std::string combinedReading = Join(m_readings.begin() + p, m_readings.begin() + p + q, m_joinSeparator); + if (!m_grid.hasNodeAtLocationSpanningLengthMatchingKey(p, q, combinedReading)) + { std::vector unigrams = m_LM->unigramsForKey(combinedReading); - - if (unigrams.size() > 0) { + + if (unigrams.size() > 0) + { Node n(combinedReading, unigrams, std::vector()); m_grid.insertNode(n, p, q); } @@ -185,21 +221,22 @@ inline void BlockReadingBuilder::build() { } } -inline const std::string BlockReadingBuilder::Join( - std::vector::const_iterator begin, +inline const std::string BlockReadingBuilder::Join(std::vector::const_iterator begin, std::vector::const_iterator end, - const std::string& separator) { + const std::string &separator) +{ std::string result; - for (std::vector::const_iterator iter = begin; iter != end;) { + for (std::vector::const_iterator iter = begin; iter != end;) + { result += *iter; ++iter; - if (iter != end) { + if (iter != end) + { result += separator; } } return result; } -} // namespace Gramambular - +} // namespace Gramambular #endif diff --git a/Source/Modules/LanguageParsers/Gramambular/Gramambular.h b/Source/Modules/LanguageParsers/Gramambular/Gramambular.h index d2601d3f..d33a298b 100644 --- a/Source/Modules/LanguageParsers/Gramambular/Gramambular.h +++ b/Source/Modules/LanguageParsers/Gramambular/Gramambular.h @@ -1,20 +1,27 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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. */ #ifndef GRAMAMBULAR_H_ diff --git a/Source/Modules/LanguageParsers/Gramambular/Grid.h b/Source/Modules/LanguageParsers/Gramambular/Grid.h index 6113bb8c..0244d076 100644 --- a/Source/Modules/LanguageParsers/Gramambular/Grid.h +++ b/Source/Modules/LanguageParsers/Gramambular/Grid.h @@ -1,20 +1,27 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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. */ #ifndef GRID_H_ @@ -27,207 +34,247 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR TH #include "NodeAnchor.h" #include "Span.h" -namespace Gramambular { +namespace Gramambular +{ -class Grid { -public: +class Grid +{ + public: void clear(); - void insertNode(const Node& node, size_t location, size_t spanningLength); - bool hasNodeAtLocationSpanningLengthMatchingKey(size_t location, - size_t spanningLength, - const std::string& key); - + void insertNode(const Node &node, size_t location, size_t spanningLength); + bool hasNodeAtLocationSpanningLengthMatchingKey(size_t location, size_t spanningLength, const std::string &key); + void expandGridByOneAtLocation(size_t location); void shrinkGridByOneAtLocation(size_t location); - + size_t width() const; std::vector nodesEndingAt(size_t location); std::vector nodesCrossingOrEndingAt(size_t location); - + // "Freeze" the node with the unigram that represents the selected candidate // value. After this, the node that contains the unigram will always be // evaluated to that unigram, while all other overlapping nodes will be reset // to their initial state (that is, if any of those nodes were "frozen" or // fixed, they will be unfrozen.) - NodeAnchor fixNodeSelectedCandidate(size_t location, - const std::string& value); - + NodeAnchor fixNodeSelectedCandidate(size_t location, const std::string &value); + // Similar to fixNodeSelectedCandidate, but instead of "freezing" the node, // only boost the unigram that represents the value with an overriding score. // This has the same side effect as fixNodeSelectedCandidate, which is that // all other overlapping nodes will be reset to their initial state. - void overrideNodeScoreForSelectedCandidate(size_t location, - const std::string& value, - float overridingScore); - - std::string dumpDOT() { + void overrideNodeScoreForSelectedCandidate(size_t location, const std::string &value, float overridingScore); + + std::string dumpDOT() + { std::stringstream sst; sst << "digraph {" << std::endl; sst << "graph [ rankdir=LR ];" << std::endl; sst << "BOS;" << std::endl; - - for (size_t p = 0; p < m_spans.size(); p++) { - Span& span = m_spans[p]; - for (size_t ni = 0; ni <= span.maximumLength(); ni++) { - Node* np = span.nodeOfLength(ni); - if (np) { - if (!p) { + + for (size_t p = 0; p < m_spans.size(); p++) + { + Span &span = m_spans[p]; + for (size_t ni = 0; ni <= span.maximumLength(); ni++) + { + Node *np = span.nodeOfLength(ni); + if (np) + { + if (!p) + { sst << "BOS -> " << np->currentKeyValue().value << ";" << std::endl; } - + sst << np->currentKeyValue().value << ";" << std::endl; - - if (p + ni < m_spans.size()) { - Span& dstSpan = m_spans[p + ni]; - for (size_t q = 0; q <= dstSpan.maximumLength(); q++) { - Node* dn = dstSpan.nodeOfLength(q); - if (dn) { - sst << np->currentKeyValue().value << " -> " - << dn->currentKeyValue().value << ";" << std::endl; + + if (p + ni < m_spans.size()) + { + Span &dstSpan = m_spans[p + ni]; + for (size_t q = 0; q <= dstSpan.maximumLength(); q++) + { + Node *dn = dstSpan.nodeOfLength(q); + if (dn) + { + sst << np->currentKeyValue().value << " -> " << dn->currentKeyValue().value << ";" + << std::endl; } } } - - if (p + ni == m_spans.size()) { + + if (p + ni == m_spans.size()) + { sst << np->currentKeyValue().value << " -> " - << "EOS;" << std::endl; + << "EOS;" << std::endl; } } } } - + sst << "EOS;" << std::endl; sst << "}"; return sst.str(); } - -protected: + + protected: std::vector m_spans; }; -inline void Grid::clear() { m_spans.clear(); } +inline void Grid::clear() +{ + m_spans.clear(); +} -inline void Grid::insertNode(const Node& node, size_t location, - size_t spanningLength) { - if (location >= m_spans.size()) { +inline void Grid::insertNode(const Node &node, size_t location, size_t spanningLength) +{ + if (location >= m_spans.size()) + { size_t diff = location - m_spans.size() + 1; - - for (size_t i = 0; i < diff; i++) { + + for (size_t i = 0; i < diff; i++) + { m_spans.push_back(Span()); } } - + m_spans[location].insertNodeOfLength(node, spanningLength); } -inline bool Grid::hasNodeAtLocationSpanningLengthMatchingKey( - size_t location, size_t spanningLength, const std::string& key) { - if (location > m_spans.size()) { +inline bool Grid::hasNodeAtLocationSpanningLengthMatchingKey(size_t location, size_t spanningLength, + const std::string &key) +{ + if (location > m_spans.size()) + { return false; } - - const Node* n = m_spans[location].nodeOfLength(spanningLength); - if (!n) { + + const Node *n = m_spans[location].nodeOfLength(spanningLength); + if (!n) + { return false; } - + return key == n->key(); } -inline void Grid::expandGridByOneAtLocation(size_t location) { - if (!location || location == m_spans.size()) { +inline void Grid::expandGridByOneAtLocation(size_t location) +{ + if (!location || location == m_spans.size()) + { m_spans.insert(m_spans.begin() + location, Span()); - } else { + } + else + { m_spans.insert(m_spans.begin() + location, Span()); - for (size_t i = 0; i < location; i++) { + for (size_t i = 0; i < location; i++) + { // zaps overlapping spans m_spans[i].removeNodeOfLengthGreaterThan(location - i); } } } -inline void Grid::shrinkGridByOneAtLocation(size_t location) { - if (location >= m_spans.size()) { +inline void Grid::shrinkGridByOneAtLocation(size_t location) +{ + if (location >= m_spans.size()) + { return; } - + m_spans.erase(m_spans.begin() + location); - for (size_t i = 0; i < location; i++) { + for (size_t i = 0; i < location; i++) + { // zaps overlapping spans m_spans[i].removeNodeOfLengthGreaterThan(location - i); } } -inline size_t Grid::width() const { return m_spans.size(); } +inline size_t Grid::width() const +{ + return m_spans.size(); +} -inline std::vector Grid::nodesEndingAt(size_t location) { +inline std::vector Grid::nodesEndingAt(size_t location) +{ std::vector result; - - if (m_spans.size() && location <= m_spans.size()) { - for (size_t i = 0; i < location; i++) { - Span& span = m_spans[i]; - if (i + span.maximumLength() >= location) { - Node* np = span.nodeOfLength(location - i); - if (np) { + + if (m_spans.size() && location <= m_spans.size()) + { + for (size_t i = 0; i < location; i++) + { + Span &span = m_spans[i]; + if (i + span.maximumLength() >= location) + { + Node *np = span.nodeOfLength(location - i); + if (np) + { NodeAnchor na; na.node = np; na.location = i; na.spanningLength = location - i; - + result.push_back(na); } } } } - + return result; } -inline std::vector Grid::nodesCrossingOrEndingAt(size_t location) { +inline std::vector Grid::nodesCrossingOrEndingAt(size_t location) +{ std::vector result; - - if (m_spans.size() && location <= m_spans.size()) { - for (size_t i = 0; i < location; i++) { - Span& span = m_spans[i]; - - if (i + span.maximumLength() >= location) { - for (size_t j = 1, m = span.maximumLength(); j <= m; j++) { - if (i + j < location) { + + if (m_spans.size() && location <= m_spans.size()) + { + for (size_t i = 0; i < location; i++) + { + Span &span = m_spans[i]; + + if (i + span.maximumLength() >= location) + { + for (size_t j = 1, m = span.maximumLength(); j <= m; j++) + { + if (i + j < location) + { continue; } - - Node* np = span.nodeOfLength(j); - if (np) { + + Node *np = span.nodeOfLength(j); + if (np) + { NodeAnchor na; na.node = np; na.location = i; na.spanningLength = location - i; - + result.push_back(na); } } } } } - + return result; } // For nodes found at the location, fix their currently-selected candidate using // the supplied string value. -inline NodeAnchor Grid::fixNodeSelectedCandidate(size_t location, - const std::string& value) { +inline NodeAnchor Grid::fixNodeSelectedCandidate(size_t location, const std::string &value) +{ std::vector nodes = nodesCrossingOrEndingAt(location); NodeAnchor node; - for (auto nodeAnchor : nodes) { + for (auto nodeAnchor : nodes) + { auto candidates = nodeAnchor.node->candidates(); - + // Reset the candidate-fixed state of every node at the location. - const_cast(nodeAnchor.node)->resetCandidate(); - - for (size_t i = 0, c = candidates.size(); i < c; ++i) { - if (candidates[i].value == value) { - const_cast(nodeAnchor.node)->selectCandidateAtIndex(i); + const_cast(nodeAnchor.node)->resetCandidate(); + + for (size_t i = 0, c = candidates.size(); i < c; ++i) + { + if (candidates[i].value == value) + { + const_cast(nodeAnchor.node)->selectCandidateAtIndex(i); node = nodeAnchor; break; } @@ -236,26 +283,28 @@ inline NodeAnchor Grid::fixNodeSelectedCandidate(size_t location, return node; } -inline void Grid::overrideNodeScoreForSelectedCandidate( - size_t location, const std::string& value, float overridingScore) { +inline void Grid::overrideNodeScoreForSelectedCandidate(size_t location, const std::string &value, + float overridingScore) +{ std::vector nodes = nodesCrossingOrEndingAt(location); - for (auto nodeAnchor : nodes) { + for (auto nodeAnchor : nodes) + { auto candidates = nodeAnchor.node->candidates(); - + // Reset the candidate-fixed state of every node at the location. - const_cast(nodeAnchor.node)->resetCandidate(); - - for (size_t i = 0, c = candidates.size(); i < c; ++i) { - if (candidates[i].value == value) { - const_cast(nodeAnchor.node) - ->selectFloatingCandidateAtIndex(i, overridingScore); + const_cast(nodeAnchor.node)->resetCandidate(); + + for (size_t i = 0, c = candidates.size(); i < c; ++i) + { + if (candidates[i].value == value) + { + const_cast(nodeAnchor.node)->selectFloatingCandidateAtIndex(i, overridingScore); break; } } } } -} // namespace Gramambular - +} // namespace Gramambular #endif diff --git a/Source/Modules/LanguageParsers/Gramambular/KeyValuePair.h b/Source/Modules/LanguageParsers/Gramambular/KeyValuePair.h index 569687ed..231d6342 100644 --- a/Source/Modules/LanguageParsers/Gramambular/KeyValuePair.h +++ b/Source/Modules/LanguageParsers/Gramambular/KeyValuePair.h @@ -1,20 +1,27 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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. */ #ifndef KEYVALUEPAIR_H_ @@ -23,36 +30,42 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR TH #include #include -namespace Gramambular { +namespace Gramambular +{ -class KeyValuePair { -public: +class KeyValuePair +{ + public: std::string key; std::string value; - - bool operator==(const KeyValuePair& another) const; - bool operator<(const KeyValuePair& another) const; + + bool operator==(const KeyValuePair &another) const; + bool operator<(const KeyValuePair &another) const; }; -inline std::ostream& operator<<(std::ostream& stream, - const KeyValuePair& pair) { +inline std::ostream &operator<<(std::ostream &stream, const KeyValuePair &pair) +{ stream << "(" << pair.key << "," << pair.value << ")"; return stream; } -inline bool KeyValuePair::operator==(const KeyValuePair& another) const { +inline bool KeyValuePair::operator==(const KeyValuePair &another) const +{ return key == another.key && value == another.value; } -inline bool KeyValuePair::operator<(const KeyValuePair& another) const { - if (key < another.key) { +inline bool KeyValuePair::operator<(const KeyValuePair &another) const +{ + if (key < another.key) + { return true; - } else if (key == another.key) { + } + else if (key == another.key) + { return value < another.value; } return false; } -} // namespace Gramambular - +} // namespace Gramambular #endif diff --git a/Source/Modules/LanguageParsers/Gramambular/LanguageModel.h b/Source/Modules/LanguageParsers/Gramambular/LanguageModel.h index bed61ab5..1049c011 100644 --- a/Source/Modules/LanguageParsers/Gramambular/LanguageModel.h +++ b/Source/Modules/LanguageParsers/Gramambular/LanguageModel.h @@ -1,20 +1,27 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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. */ #ifndef LANGUAGEMODEL_H_ @@ -26,18 +33,20 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR TH #include "Bigram.h" #include "Unigram.h" -namespace Gramambular { +namespace Gramambular +{ -class LanguageModel { -public: - virtual ~LanguageModel() {} - - virtual const std::vector bigramsForKeys( - const std::string& preceedingKey, const std::string& key) = 0; - virtual const std::vector unigramsForKey(const std::string& key) = 0; - virtual bool hasUnigramsForKey(const std::string& key) = 0; +class LanguageModel +{ + public: + virtual ~LanguageModel() + { + } + + virtual const std::vector bigramsForKeys(const std::string &preceedingKey, const std::string &key) = 0; + virtual const std::vector unigramsForKey(const std::string &key) = 0; + virtual bool hasUnigramsForKey(const std::string &key) = 0; }; -} // namespace Gramambular - +} // namespace Gramambular #endif diff --git a/Source/Modules/LanguageParsers/Gramambular/Node.h b/Source/Modules/LanguageParsers/Gramambular/Node.h index 42de5910..16b69fdf 100644 --- a/Source/Modules/LanguageParsers/Gramambular/Node.h +++ b/Source/Modules/LanguageParsers/Gramambular/Node.h @@ -1,20 +1,27 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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. */ #ifndef NODE_H_ @@ -27,105 +34,105 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR TH #include "LanguageModel.h" -namespace Gramambular { +namespace Gramambular +{ -class Node { -public: +class Node +{ + public: Node(); - Node(const std::string& key, const std::vector& unigrams, - const std::vector& bigrams); - - void primeNodeWithPreceedingKeyValues( - const std::vector& keyValues); - + Node(const std::string &key, const std::vector &unigrams, const std::vector &bigrams); + + void primeNodeWithPreceedingKeyValues(const std::vector &keyValues); + bool isCandidateFixed() const; - const std::vector& candidates() const; + const std::vector &candidates() const; void selectCandidateAtIndex(size_t index = 0, bool fix = true); void resetCandidate(); void selectFloatingCandidateAtIndex(size_t index, double score); - - const std::string& key() const; + + const std::string &key() const; double score() const; - double scoreForCandidate(const std::string& candidate) const; + double scoreForCandidate(const std::string &candidate) const; const KeyValuePair currentKeyValue() const; double highestUnigramScore() const; - -protected: - const LanguageModel* m_LM; - + + protected: + const LanguageModel *m_LM; + std::string m_key; double m_score; - + std::vector m_unigrams; std::vector m_candidates; std::map m_valueUnigramIndexMap; - std::map > m_preceedingGramBigramMap; - + std::map> m_preceedingGramBigramMap; + bool m_candidateFixed; size_t m_selectedUnigramIndex; - - friend std::ostream& operator<<(std::ostream& stream, const Node& node); + + friend std::ostream &operator<<(std::ostream &stream, const Node &node); }; -inline std::ostream& operator<<(std::ostream& stream, const Node& node) { - stream << "(node,key:" << node.m_key - << ",fixed:" << (node.m_candidateFixed ? "true" : "false") - << ",selected:" << node.m_selectedUnigramIndex << "," - << node.m_unigrams << ")"; +inline std::ostream &operator<<(std::ostream &stream, const Node &node) +{ + stream << "(node,key:" << node.m_key << ",fixed:" << (node.m_candidateFixed ? "true" : "false") + << ",selected:" << node.m_selectedUnigramIndex << "," << node.m_unigrams << ")"; return stream; } -inline Node::Node() -: m_candidateFixed(false), m_selectedUnigramIndex(0), m_score(0.0) {} +inline Node::Node() : m_candidateFixed(false), m_selectedUnigramIndex(0), m_score(0.0) +{ +} -inline Node::Node(const std::string& key, const std::vector& unigrams, - const std::vector& bigrams) -: m_key(key), -m_unigrams(unigrams), -m_candidateFixed(false), -m_selectedUnigramIndex(0), -m_score(0.0) { +inline Node::Node(const std::string &key, const std::vector &unigrams, const std::vector &bigrams) + : m_key(key), m_unigrams(unigrams), m_candidateFixed(false), m_selectedUnigramIndex(0), m_score(0.0) +{ stable_sort(m_unigrams.begin(), m_unigrams.end(), Unigram::ScoreCompare); - - if (m_unigrams.size()) { + + if (m_unigrams.size()) + { m_score = m_unigrams[0].score; } - + size_t i = 0; - for (std::vector::const_iterator ui = m_unigrams.begin(); - ui != m_unigrams.end(); ++ui) { + for (std::vector::const_iterator ui = m_unigrams.begin(); ui != m_unigrams.end(); ++ui) + { m_valueUnigramIndexMap[(*ui).keyValue.value] = i; i++; - + m_candidates.push_back((*ui).keyValue); } - - for (std::vector::const_iterator bi = bigrams.begin(); - bi != bigrams.end(); ++bi) { + + for (std::vector::const_iterator bi = bigrams.begin(); bi != bigrams.end(); ++bi) + { m_preceedingGramBigramMap[(*bi).preceedingKeyValue].push_back(*bi); } } -inline void Node::primeNodeWithPreceedingKeyValues( - const std::vector& keyValues) { +inline void Node::primeNodeWithPreceedingKeyValues(const std::vector &keyValues) +{ size_t newIndex = m_selectedUnigramIndex; double max = m_score; - - if (!isCandidateFixed()) { - for (std::vector::const_iterator kvi = keyValues.begin(); - kvi != keyValues.end(); ++kvi) { - std::map >::const_iterator f = - m_preceedingGramBigramMap.find(*kvi); - if (f != m_preceedingGramBigramMap.end()) { - const std::vector& bigrams = (*f).second; - - for (std::vector::const_iterator bi = bigrams.begin(); - bi != bigrams.end(); ++bi) { - const Bigram& bigram = *bi; - if (bigram.score > max) { + + if (!isCandidateFixed()) + { + for (std::vector::const_iterator kvi = keyValues.begin(); kvi != keyValues.end(); ++kvi) + { + std::map>::const_iterator f = m_preceedingGramBigramMap.find(*kvi); + if (f != m_preceedingGramBigramMap.end()) + { + const std::vector &bigrams = (*f).second; + + for (std::vector::const_iterator bi = bigrams.begin(); bi != bigrams.end(); ++bi) + { + const Bigram &bigram = *bi; + if (bigram.score > max) + { std::map::const_iterator uf = - m_valueUnigramIndexMap.find((*bi).keyValue.value); - if (uf != m_valueUnigramIndexMap.end()) { + m_valueUnigramIndexMap.find((*bi).keyValue.value); + if (uf != m_valueUnigramIndexMap.end()) + { newIndex = (*uf).second; max = bigram.score; } @@ -134,80 +141,109 @@ inline void Node::primeNodeWithPreceedingKeyValues( } } } - - if (m_score != max) { + + if (m_score != max) + { m_score = max; } - - if (newIndex != m_selectedUnigramIndex) { + + if (newIndex != m_selectedUnigramIndex) + { m_selectedUnigramIndex = newIndex; } } -inline bool Node::isCandidateFixed() const { return m_candidateFixed; } +inline bool Node::isCandidateFixed() const +{ + return m_candidateFixed; +} -inline const std::vector& Node::candidates() const { +inline const std::vector &Node::candidates() const +{ return m_candidates; } -inline void Node::selectCandidateAtIndex(size_t index, bool fix) { - if (index >= m_unigrams.size()) { +inline void Node::selectCandidateAtIndex(size_t index, bool fix) +{ + if (index >= m_unigrams.size()) + { m_selectedUnigramIndex = 0; - } else { + } + else + { m_selectedUnigramIndex = index; } - + m_candidateFixed = fix; m_score = 99; } -inline void Node::resetCandidate() { +inline void Node::resetCandidate() +{ m_selectedUnigramIndex = 0; m_candidateFixed = 0; - if (m_unigrams.size()) { + if (m_unigrams.size()) + { m_score = m_unigrams[0].score; } } -inline void Node::selectFloatingCandidateAtIndex(size_t index, double score) { - if (index >= m_unigrams.size()) { +inline void Node::selectFloatingCandidateAtIndex(size_t index, double score) +{ + if (index >= m_unigrams.size()) + { m_selectedUnigramIndex = 0; - } else { + } + else + { m_selectedUnigramIndex = index; } m_candidateFixed = false; m_score = score; } -inline const std::string& Node::key() const { return m_key; } +inline const std::string &Node::key() const +{ + return m_key; +} -inline double Node::score() const { return m_score; } +inline double Node::score() const +{ + return m_score; +} - -inline double Node::scoreForCandidate(const std::string& candidate) const { - for (auto unigram : m_unigrams) { - if (unigram.keyValue.value == candidate) { +inline double Node::scoreForCandidate(const std::string &candidate) const +{ + for (auto unigram : m_unigrams) + { + if (unigram.keyValue.value == candidate) + { return unigram.score; } } return 0.0; } -inline double Node::highestUnigramScore() const { - if (m_unigrams.empty()) { +inline double Node::highestUnigramScore() const +{ + if (m_unigrams.empty()) + { return 0.0; } return m_unigrams[0].score; } -inline const KeyValuePair Node::currentKeyValue() const { - if (m_selectedUnigramIndex >= m_unigrams.size()) { +inline const KeyValuePair Node::currentKeyValue() const +{ + if (m_selectedUnigramIndex >= m_unigrams.size()) + { return KeyValuePair(); - } else { + } + else + { return m_candidates[m_selectedUnigramIndex]; } } -} // namespace Gramambular - +} // namespace Gramambular #endif diff --git a/Source/Modules/LanguageParsers/Gramambular/NodeAnchor.h b/Source/Modules/LanguageParsers/Gramambular/NodeAnchor.h index 485bb51f..432566a0 100644 --- a/Source/Modules/LanguageParsers/Gramambular/NodeAnchor.h +++ b/Source/Modules/LanguageParsers/Gramambular/NodeAnchor.h @@ -1,20 +1,27 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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. */ #ifndef NODEANCHOR_H_ @@ -24,40 +31,45 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR TH #include "Node.h" -namespace Gramambular { +namespace Gramambular +{ -struct NodeAnchor { - const Node* node = nullptr; +struct NodeAnchor +{ + const Node *node = nullptr; size_t location = 0; size_t spanningLength = 0; double accumulatedScore = 0.0; }; -inline std::ostream& operator<<(std::ostream& stream, - const NodeAnchor& anchor) { +inline std::ostream &operator<<(std::ostream &stream, const NodeAnchor &anchor) +{ stream << "{@(" << anchor.location << "," << anchor.spanningLength << "),"; - if (anchor.node) { + if (anchor.node) + { stream << *(anchor.node); - } else { + } + else + { stream << "null"; } stream << "}"; return stream; } -inline std::ostream& operator<<(std::ostream& stream, - const std::vector& anchor) { - for (std::vector::const_iterator i = anchor.begin(); - i != anchor.end(); ++i) { +inline std::ostream &operator<<(std::ostream &stream, const std::vector &anchor) +{ + for (std::vector::const_iterator i = anchor.begin(); i != anchor.end(); ++i) + { stream << *i; - if (i + 1 != anchor.end()) { + if (i + 1 != anchor.end()) + { stream << "<-"; } } - + return stream; } -} // namespace Gramambular - +} // namespace Gramambular #endif diff --git a/Source/Modules/LanguageParsers/Gramambular/Span.h b/Source/Modules/LanguageParsers/Gramambular/Span.h index 30c12692..57c9a64c 100644 --- a/Source/Modules/LanguageParsers/Gramambular/Span.h +++ b/Source/Modules/LanguageParsers/Gramambular/Span.h @@ -1,20 +1,27 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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. */ #ifndef SPAN_H_ @@ -26,67 +33,80 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR TH #include "Node.h" -namespace Gramambular { -class Span { -public: +namespace Gramambular +{ +class Span +{ + public: void clear(); - void insertNodeOfLength(const Node& node, size_t length); + void insertNodeOfLength(const Node &node, size_t length); void removeNodeOfLengthGreaterThan(size_t length); - - Node* nodeOfLength(size_t length); + + Node *nodeOfLength(size_t length); size_t maximumLength() const; - -protected: + + protected: std::map m_lengthNodeMap; size_t m_maximumLength = 0; }; -inline void Span::clear() { +inline void Span::clear() +{ m_lengthNodeMap.clear(); m_maximumLength = 0; } -inline void Span::insertNodeOfLength(const Node& node, size_t length) { +inline void Span::insertNodeOfLength(const Node &node, size_t length) +{ m_lengthNodeMap[length] = node; - if (length > m_maximumLength) { + if (length > m_maximumLength) + { m_maximumLength = length; } } -inline void Span::removeNodeOfLengthGreaterThan(size_t length) { - if (length > m_maximumLength) { +inline void Span::removeNodeOfLengthGreaterThan(size_t length) +{ + if (length > m_maximumLength) + { return; } - + size_t max = 0; std::set removeSet; - for (std::map::iterator i = m_lengthNodeMap.begin(), - e = m_lengthNodeMap.end(); - i != e; ++i) { - if ((*i).first > length) { + for (std::map::iterator i = m_lengthNodeMap.begin(), e = m_lengthNodeMap.end(); i != e; ++i) + { + if ((*i).first > length) + { removeSet.insert((*i).first); - } else { - if ((*i).first > max) { + } + else + { + if ((*i).first > max) + { max = (*i).first; } } } - - for (std::set::iterator i = removeSet.begin(), e = removeSet.end(); - i != e; ++i) { + + for (std::set::iterator i = removeSet.begin(), e = removeSet.end(); i != e; ++i) + { m_lengthNodeMap.erase(*i); } - + m_maximumLength = max; } -inline Node* Span::nodeOfLength(size_t length) { +inline Node *Span::nodeOfLength(size_t length) +{ std::map::iterator f = m_lengthNodeMap.find(length); return f == m_lengthNodeMap.end() ? 0 : &(*f).second; } -inline size_t Span::maximumLength() const { return m_maximumLength; } -} // namespace Gramambular - +inline size_t Span::maximumLength() const +{ + return m_maximumLength; +} +} // namespace Gramambular #endif diff --git a/Source/Modules/LanguageParsers/Gramambular/Unigram.h b/Source/Modules/LanguageParsers/Gramambular/Unigram.h index 45b461d4..7faac48d 100644 --- a/Source/Modules/LanguageParsers/Gramambular/Unigram.h +++ b/Source/Modules/LanguageParsers/Gramambular/Unigram.h @@ -1,20 +1,27 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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. */ #ifndef UNIGRAM_H_ @@ -24,22 +31,25 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR TH #include "KeyValuePair.h" -namespace Gramambular { +namespace Gramambular +{ -class Unigram { -public: +class Unigram +{ + public: Unigram(); - + KeyValuePair keyValue; double score; - - bool operator==(const Unigram& another) const; - bool operator<(const Unigram& another) const; - - static bool ScoreCompare(const Unigram& a, const Unigram& b); + + bool operator==(const Unigram &another) const; + bool operator<(const Unigram &another) const; + + static bool ScoreCompare(const Unigram &a, const Unigram &b); }; -inline std::ostream& operator<<(std::ostream& stream, const Unigram& gram) { +inline std::ostream &operator<<(std::ostream &stream, const Unigram &gram) +{ std::streamsize p = stream.precision(); stream.precision(6); stream << "(" << gram.keyValue << "," << gram.score << ")"; @@ -47,44 +57,52 @@ inline std::ostream& operator<<(std::ostream& stream, const Unigram& gram) { return stream; } -inline std::ostream& operator<<(std::ostream& stream, - const std::vector& grams) { +inline std::ostream &operator<<(std::ostream &stream, const std::vector &grams) +{ stream << "[" << grams.size() << "]=>{"; - + size_t index = 0; - - for (std::vector::const_iterator gi = grams.begin(); - gi != grams.end(); ++gi, ++index) { + + for (std::vector::const_iterator gi = grams.begin(); gi != grams.end(); ++gi, ++index) + { stream << index << "=>"; stream << *gi; - if (gi + 1 != grams.end()) { + if (gi + 1 != grams.end()) + { stream << ","; } } - + stream << "}"; return stream; } -inline Unigram::Unigram() : score(0.0) {} +inline Unigram::Unigram() : score(0.0) +{ +} -inline bool Unigram::operator==(const Unigram& another) const { +inline bool Unigram::operator==(const Unigram &another) const +{ return keyValue == another.keyValue && score == another.score; } -inline bool Unigram::operator<(const Unigram& another) const { - if (keyValue < another.keyValue) { +inline bool Unigram::operator<(const Unigram &another) const +{ + if (keyValue < another.keyValue) + { return true; - } else if (keyValue == another.keyValue) { + } + else if (keyValue == another.keyValue) + { return score < another.score; } return false; } -inline bool Unigram::ScoreCompare(const Unigram& a, const Unigram& b) { +inline bool Unigram::ScoreCompare(const Unigram &a, const Unigram &b) +{ return a.score > b.score; } -} // namespace Gramambular - +} // namespace Gramambular #endif diff --git a/Source/Modules/LanguageParsers/Gramambular/Walker.h b/Source/Modules/LanguageParsers/Gramambular/Walker.h index b694c7e7..c5ef2e3d 100644 --- a/Source/Modules/LanguageParsers/Gramambular/Walker.h +++ b/Source/Modules/LanguageParsers/Gramambular/Walker.h @@ -1,20 +1,27 @@ // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). -// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). /* -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: +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: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, - except as required to fulfill notice requirements above. +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. -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. +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. */ #ifndef WALKER_H_ @@ -25,60 +32,65 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR TH #include "Grid.h" -namespace Gramambular { +namespace Gramambular +{ -class Walker { -public: - explicit Walker(Grid* inGrid); - const std::vector reverseWalk(size_t location, - double accumulatedScore = 0.0); - -protected: - Grid* m_grid; +class Walker +{ + public: + explicit Walker(Grid *inGrid); + const std::vector reverseWalk(size_t location, double accumulatedScore = 0.0); + + protected: + Grid *m_grid; }; -inline Walker::Walker(Grid* inGrid) : m_grid(inGrid) {} +inline Walker::Walker(Grid *inGrid) : m_grid(inGrid) +{ +} -inline const std::vector Walker::reverseWalk( - size_t location, double accumulatedScore) { - if (!location || location > m_grid->width()) { +inline const std::vector Walker::reverseWalk(size_t location, double accumulatedScore) +{ + if (!location || location > m_grid->width()) + { return std::vector(); } - - std::vector > paths; - + + std::vector> paths; + std::vector nodes = m_grid->nodesEndingAt(location); - - for (std::vector::iterator ni = nodes.begin(); ni != nodes.end(); - ++ni) { - if (!(*ni).node) { + + for (std::vector::iterator ni = nodes.begin(); ni != nodes.end(); ++ni) + { + if (!(*ni).node) + { continue; } - + (*ni).accumulatedScore = accumulatedScore + (*ni).node->score(); - - std::vector path = - reverseWalk(location - (*ni).spanningLength, (*ni).accumulatedScore); + + std::vector path = reverseWalk(location - (*ni).spanningLength, (*ni).accumulatedScore); path.insert(path.begin(), *ni); - + paths.push_back(path); } - - if (!paths.size()) { + + if (!paths.size()) + { return std::vector(); } - - std::vector* result = &*(paths.begin()); - for (std::vector >::iterator pi = paths.begin(); - pi != paths.end(); ++pi) { - if ((*pi).back().accumulatedScore > result->back().accumulatedScore) { + + std::vector *result = &*(paths.begin()); + for (std::vector>::iterator pi = paths.begin(); pi != paths.end(); ++pi) + { + if ((*pi).back().accumulatedScore > result->back().accumulatedScore) + { result = &*pi; } } - + return *result; } -} // namespace Gramambular - +} // namespace Gramambular #endif From 562734d7dfb8f8d08f9c2920ce25e2b169cac53a Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sun, 3 Apr 2022 14:49:52 +0800 Subject: [PATCH 09/16] Makefile // Clang-Format & Swift Lint. --- Makefile | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Makefile b/Makefile index 5bb75a65..57943fb0 100644 --- a/Makefile +++ b/Makefile @@ -20,6 +20,22 @@ debug: DSTROOT = /Library/Input Methods VC_APP_ROOT = $(DSTROOT)/vChewing.app +.PHONY: clang-format lint + +clang-format: + @swift-format format --in-place --configuration ./.clang-format-swift.json --recursive ./DataCompiler/ + @swift-format format --in-place --configuration ./.clang-format-swift.json --recursive ./Installer/ + @swift-format format --in-place --configuration ./.clang-format-swift.json --recursive ./Source/ + @swift-format format --in-place --configuration ./.clang-format-swift.json --recursive ./UserPhraseEditor/ + @find ./Installer/ -iname '*.h' -o -iname '*.m' | xargs clang-format -i -style=Microsoft + @find ./Source/3rdParty/OVMandarin -iname '*.h' -o -iname '*.cpp' -o -iname '*.mm' -o -iname '*.m' | xargs clang-format -i -style=Microsoft + @find ./Source/Modules/ -iname '*.h' -o -iname '*.cpp' -o -iname '*.mm' -o -iname '*.m' | xargs clang-format -i -style=Microsoft +lint: + @swift-format lint --configuration ./.clang-format-swift.json --recursive ./DataCompiler/ + @swift-format lint --configuration ./.clang-format-swift.json --recursive ./Installer/ + @swift-format lint --configuration ./.clang-format-swift.json --recursive ./Source/ + @swift-format lint --configuration ./.clang-format-swift.json --recursive ./UserPhraseEditor/ + .PHONY: permission-check install-debug install-release permission-check: From 7f3e6dc0fa04a14eee8df1d2a9ac8d969dfdee21 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sun, 3 Apr 2022 15:17:50 +0800 Subject: [PATCH 10/16] Readme // Add explanations regarding how to clang-format. --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index e140742e..5b1b6fd3 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,18 @@ 使用者可自由使用、散播本軟體,惟散播時必須完整保留版權聲明及軟體授權、且一旦經過修改便不可以再繼續使用威注音的產品名稱。 +## 格式規範: + +該專案對源碼格式有規範,且 Swift 與其他 (Obj)C(++) 系語言持不同規範: + +- Swift: 採 [Apple 官方 Swift-Format](https://github.com/apple/swift-format),且施加如下例外修改項目: + - Indentation 僅使用 `"indentation" : { "tabs" : 1 },`,不以空格來縮進。 + - `"indentSwitchCaseLabels" : true,` + - `"lineLength" : 120,` + - `"NoBlockComments" : false,` + - `"tabWidth" : 4,` +- (Obj)C(++) 系語言:使用 clang-format 命令、且採 Microsoft 行文規範。該規範以四個西文半形空格為行縮進單位。 + ## 特殊勸告 為了您的精神衛生,任何使用威注音輸入法時遇到的產品問題、請勿提報至小麥注音,除非您確信小麥注音也有該問題。即便如此,也請在他們那邊不要提及威注音。 From f718a8799183b3f2a555f3447596f764efbc6b01 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Mon, 4 Apr 2022 13:21:27 +0800 Subject: [PATCH 11/16] Proj // Shorten the naming of Candidate Controllers. --- Source/Modules/ControllerModules/KeyHandler.h | 6 +- .../Modules/ControllerModules/KeyHandler.mm | 78 +++++++++---------- .../Modules/IMEModules/ctlInputMethod.swift | 70 ++++++++--------- ...ateController.swift => ctlCandidate.swift} | 18 ++--- ...ler.swift => ctlCandidateHorizontal.swift} | 9 +-- ...oller.swift => ctlCandidateVertical.swift} | 9 +-- vChewing.xcodeproj/project.pbxproj | 24 +++--- 7 files changed, 103 insertions(+), 111 deletions(-) rename Source/UI/CandidateUI/{CandidateController.swift => ctlCandidate.swift} (91%) rename Source/UI/CandidateUI/{HorizontalCandidateController.swift => ctlCandidateHorizontal.swift} (98%) rename Source/UI/CandidateUI/{VerticalCandidateController.swift => ctlCandidateVertical.swift} (98%) diff --git a/Source/Modules/ControllerModules/KeyHandler.h b/Source/Modules/ControllerModules/KeyHandler.h index 6b6116d0..9e1db717 100644 --- a/Source/Modules/ControllerModules/KeyHandler.h +++ b/Source/Modules/ControllerModules/KeyHandler.h @@ -39,10 +39,8 @@ extern InputMode imeModeNULL; @class KeyHandler; @protocol KeyHandlerDelegate -- (id)candidateControllerForKeyHandler:(KeyHandler *)keyHandler; -- (void)keyHandler:(KeyHandler *)keyHandler - didSelectCandidateAtIndex:(NSInteger)index - candidateController:(id)controller; +- (id)ctlCandidateForKeyHandler:(KeyHandler *)keyHandler; +- (void)keyHandler:(KeyHandler *)keyHandler didSelectCandidateAtIndex:(NSInteger)index ctlCandidate:(id)controller; - (BOOL)keyHandler:(KeyHandler *)keyHandler didRequestWriteUserPhraseWithState:(InputState *)state; @end diff --git a/Source/Modules/ControllerModules/KeyHandler.mm b/Source/Modules/ControllerModules/KeyHandler.mm index c2eb3b12..0f638936 100644 --- a/Source/Modules/ControllerModules/KeyHandler.mm +++ b/Source/Modules/ControllerModules/KeyHandler.mm @@ -1261,7 +1261,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; { NSString *inputText = input.inputText; UniChar charCode = input.charCode; - VTCandidateController *gCurrentCandidateController = [self.delegate candidateControllerForKeyHandler:self]; + ctlCandidate *ctlCandidateCurrent = [self.delegate ctlCandidateForKeyHandler:self]; BOOL cancelCandidateKey = [input isBackSpace] || [input isESC] || [input isDelete] || (([input isCursorBackward] || [input isCursorForward]) && [input isShiftHold]); @@ -1306,18 +1306,18 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; return YES; } [self.delegate keyHandler:self - didSelectCandidateAtIndex:gCurrentCandidateController.selectedCandidateIndex - candidateController:gCurrentCandidateController]; + didSelectCandidateAtIndex:ctlCandidateCurrent.selectedCandidateIndex + ctlCandidate:ctlCandidateCurrent]; return YES; } if ([input isTab]) { - BOOL updated = mgrPrefs.specifyTabKeyBehavior - ? ([input isShiftHold] ? [gCurrentCandidateController showPreviousPage] - : [gCurrentCandidateController showNextPage]) - : ([input isShiftHold] ? [gCurrentCandidateController highlightPreviousCandidate] - : [gCurrentCandidateController highlightNextCandidate]); + BOOL updated = + mgrPrefs.specifyTabKeyBehavior + ? ([input isShiftHold] ? [ctlCandidateCurrent showPreviousPage] : [ctlCandidateCurrent showNextPage]) + : ([input isShiftHold] ? [ctlCandidateCurrent highlightPreviousCandidate] + : [ctlCandidateCurrent highlightNextCandidate]); if (!updated) { [IME prtDebugIntel:@"9B691919"]; @@ -1329,10 +1329,10 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; if ([input isSpace]) { BOOL updated = mgrPrefs.specifySpaceKeyBehavior - ? ([input isShiftHold] ? [gCurrentCandidateController highlightNextCandidate] - : [gCurrentCandidateController showNextPage]) - : ([input isShiftHold] ? [gCurrentCandidateController showNextPage] - : [gCurrentCandidateController highlightNextCandidate]); + ? ([input isShiftHold] ? [ctlCandidateCurrent highlightNextCandidate] + : [ctlCandidateCurrent showNextPage]) + : ([input isShiftHold] ? [ctlCandidateCurrent showNextPage] + : [ctlCandidateCurrent highlightNextCandidate]); if (!updated) { [IME prtDebugIntel:@"A11C781F"]; @@ -1343,7 +1343,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; if ([input isPageDown] || input.emacsKey == vChewingEmacsKeyNextPage) { - BOOL updated = [gCurrentCandidateController showNextPage]; + BOOL updated = [ctlCandidateCurrent showNextPage]; if (!updated) { [IME prtDebugIntel:@"9B691919"]; @@ -1354,7 +1354,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; if ([input isPageUp]) { - BOOL updated = [gCurrentCandidateController showPreviousPage]; + BOOL updated = [ctlCandidateCurrent showPreviousPage]; if (!updated) { [IME prtDebugIntel:@"9569955D"]; @@ -1365,9 +1365,9 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; if ([input isLeft]) { - if ([gCurrentCandidateController isKindOfClass:[VTHorizontalCandidateController class]]) + if ([ctlCandidateCurrent isKindOfClass:[ctlCandidateHorizontal class]]) { - BOOL updated = [gCurrentCandidateController highlightPreviousCandidate]; + BOOL updated = [ctlCandidateCurrent highlightPreviousCandidate]; if (!updated) { [IME prtDebugIntel:@"1145148D"]; @@ -1376,7 +1376,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; } else { - BOOL updated = [gCurrentCandidateController showPreviousPage]; + BOOL updated = [ctlCandidateCurrent showPreviousPage]; if (!updated) { [IME prtDebugIntel:@"1919810D"]; @@ -1388,7 +1388,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; if (input.emacsKey == vChewingEmacsKeyBackward) { - BOOL updated = [gCurrentCandidateController highlightPreviousCandidate]; + BOOL updated = [ctlCandidateCurrent highlightPreviousCandidate]; if (!updated) { [IME prtDebugIntel:@"9B89308D"]; @@ -1399,9 +1399,9 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; if ([input isRight]) { - if ([gCurrentCandidateController isKindOfClass:[VTHorizontalCandidateController class]]) + if ([ctlCandidateCurrent isKindOfClass:[ctlCandidateHorizontal class]]) { - BOOL updated = [gCurrentCandidateController highlightNextCandidate]; + BOOL updated = [ctlCandidateCurrent highlightNextCandidate]; if (!updated) { [IME prtDebugIntel:@"9B65138D"]; @@ -1410,7 +1410,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; } else { - BOOL updated = [gCurrentCandidateController showNextPage]; + BOOL updated = [ctlCandidateCurrent showNextPage]; if (!updated) { [IME prtDebugIntel:@"9244908D"]; @@ -1422,7 +1422,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; if (input.emacsKey == vChewingEmacsKeyForward) { - BOOL updated = [gCurrentCandidateController highlightNextCandidate]; + BOOL updated = [ctlCandidateCurrent highlightNextCandidate]; if (!updated) { [IME prtDebugIntel:@"9B2428D"]; @@ -1433,9 +1433,9 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; if ([input isUp]) { - if ([gCurrentCandidateController isKindOfClass:[VTHorizontalCandidateController class]]) + if ([ctlCandidateCurrent isKindOfClass:[ctlCandidateHorizontal class]]) { - BOOL updated = [gCurrentCandidateController showPreviousPage]; + BOOL updated = [ctlCandidateCurrent showPreviousPage]; if (!updated) { [IME prtDebugIntel:@"9B614524"]; @@ -1444,7 +1444,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; } else { - BOOL updated = [gCurrentCandidateController highlightPreviousCandidate]; + BOOL updated = [ctlCandidateCurrent highlightPreviousCandidate]; if (!updated) { [IME prtDebugIntel:@"ASD9908D"]; @@ -1456,9 +1456,9 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; if ([input isDown]) { - if ([gCurrentCandidateController isKindOfClass:[VTHorizontalCandidateController class]]) + if ([ctlCandidateCurrent isKindOfClass:[ctlCandidateHorizontal class]]) { - BOOL updated = [gCurrentCandidateController showNextPage]; + BOOL updated = [ctlCandidateCurrent showNextPage]; if (!updated) { [IME prtDebugIntel:@"92B990DD"]; @@ -1467,7 +1467,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; } else { - BOOL updated = [gCurrentCandidateController highlightNextCandidate]; + BOOL updated = [ctlCandidateCurrent highlightNextCandidate]; if (!updated) { [IME prtDebugIntel:@"6B99908D"]; @@ -1479,14 +1479,14 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; if ([input isHome] || input.emacsKey == vChewingEmacsKeyHome) { - if (gCurrentCandidateController.selectedCandidateIndex == 0) + if (ctlCandidateCurrent.selectedCandidateIndex == 0) { [IME prtDebugIntel:@"9B6EDE8D"]; errorCallback(); } else { - gCurrentCandidateController.selectedCandidateIndex = 0; + ctlCandidateCurrent.selectedCandidateIndex = 0; } return YES; @@ -1510,14 +1510,14 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; if (([input isEnd] || input.emacsKey == vChewingEmacsKeyEnd) && candidates.count > 0) { - if (gCurrentCandidateController.selectedCandidateIndex == candidates.count - 1) + if (ctlCandidateCurrent.selectedCandidateIndex == candidates.count - 1) { [IME prtDebugIntel:@"9B69AAAD"]; errorCallback(); } else { - gCurrentCandidateController.selectedCandidateIndex = candidates.count - 1; + ctlCandidateCurrent.selectedCandidateIndex = candidates.count - 1; } return YES; } @@ -1541,9 +1541,9 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; match = inputText; } - for (NSUInteger j = 0, c = [gCurrentCandidateController.keyLabels count]; j < c; j++) + for (NSUInteger j = 0, c = [ctlCandidateCurrent.keyLabels count]; j < c; j++) { - VTCandidateKeyLabel *label = gCurrentCandidateController.keyLabels[j]; + VTCandidateKeyLabel *label = ctlCandidateCurrent.keyLabels[j]; if ([match compare:label.key options:NSCaseInsensitiveSearch] == NSOrderedSame) { index = j; @@ -1553,12 +1553,10 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; if (index != NSNotFound) { - NSUInteger candidateIndex = [gCurrentCandidateController candidateIndexAtKeyLabelIndex:index]; + NSUInteger candidateIndex = [ctlCandidateCurrent candidateIndexAtKeyLabelIndex:index]; if (candidateIndex != NSUIntegerMax) { - [self.delegate keyHandler:self - didSelectCandidateAtIndex:candidateIndex - candidateController:gCurrentCandidateController]; + [self.delegate keyHandler:self didSelectCandidateAtIndex:candidateIndex ctlCandidate:ctlCandidateCurrent]; return YES; } } @@ -1606,12 +1604,12 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; if (shouldAutoSelectCandidate) { - NSUInteger candidateIndex = [gCurrentCandidateController candidateIndexAtKeyLabelIndex:0]; + NSUInteger candidateIndex = [ctlCandidateCurrent candidateIndexAtKeyLabelIndex:0]; if (candidateIndex != NSUIntegerMax) { [self.delegate keyHandler:self didSelectCandidateAtIndex:candidateIndex - candidateController:gCurrentCandidateController]; + ctlCandidate:ctlCandidateCurrent]; [self clear]; InputStateEmptyIgnoringPreviousState *empty = [[InputStateEmptyIgnoringPreviousState alloc] init]; stateCallback(empty); diff --git a/Source/Modules/IMEModules/ctlInputMethod.swift b/Source/Modules/IMEModules/ctlInputMethod.swift index 4bfc6675..73f2c310 100644 --- a/Source/Modules/IMEModules/ctlInputMethod.swift +++ b/Source/Modules/IMEModules/ctlInputMethod.swift @@ -35,11 +35,11 @@ extension Bool { private let kMinKeyLabelSize: CGFloat = 10 -private var gCurrentCandidateController: CandidateController? +private var ctlCandidateCurrent: ctlCandidate? -extension CandidateController { - fileprivate static let horizontal = HorizontalCandidateController() - fileprivate static let vertical = VerticalCandidateController() +extension ctlCandidate { + fileprivate static let horizontal = ctlCandidateHorizontal() + fileprivate static let vertical = ctlCandidateVertical() } @objc(ctlInputMethod) @@ -500,8 +500,8 @@ extension ctlInputMethod { private func handle(state: InputState.Deactivated, previous: InputState, client: Any?) { currentCandidateClient = nil - gCurrentCandidateController?.delegate = nil - gCurrentCandidateController?.visible = false + ctlCandidateCurrent?.delegate = nil + ctlCandidateCurrent?.visible = false hideTooltip() if let previous = previous as? InputState.NotEmpty { @@ -513,7 +513,7 @@ extension ctlInputMethod { } private func handle(state: InputState.Empty, previous: InputState, client: Any?) { - gCurrentCandidateController?.visible = false + ctlCandidateCurrent?.visible = false hideTooltip() guard let client = client as? IMKTextInput else { @@ -531,7 +531,7 @@ extension ctlInputMethod { private func handle( state: InputState.EmptyIgnoringPreviousState, previous: InputState, client: Any! ) { - gCurrentCandidateController?.visible = false + ctlCandidateCurrent?.visible = false hideTooltip() guard let client = client as? IMKTextInput else { @@ -544,7 +544,7 @@ extension ctlInputMethod { } private func handle(state: InputState.Committing, previous: InputState, client: Any?) { - gCurrentCandidateController?.visible = false + ctlCandidateCurrent?.visible = false hideTooltip() guard let client = client as? IMKTextInput else { @@ -561,7 +561,7 @@ extension ctlInputMethod { } private func handle(state: InputState.Inputting, previous: InputState, client: Any?) { - gCurrentCandidateController?.visible = false + ctlCandidateCurrent?.visible = false hideTooltip() guard let client = client as? IMKTextInput else { @@ -586,7 +586,7 @@ extension ctlInputMethod { } private func handle(state: InputState.Marking, previous: InputState, client: Any?) { - gCurrentCandidateController?.visible = false + ctlCandidateCurrent?.visible = false guard let client = client as? IMKTextInput else { hideTooltip() return @@ -610,7 +610,7 @@ extension ctlInputMethod { private func handle(state: InputState.ChoosingCandidate, previous: InputState, client: Any?) { hideTooltip() guard let client = client as? IMKTextInput else { - gCurrentCandidateController?.visible = false + ctlCandidateCurrent?.visible = false return } @@ -625,7 +625,7 @@ extension ctlInputMethod { private func handle(state: InputState.AssociatedPhrases, previous: InputState, client: Any?) { hideTooltip() guard let client = client as? IMKTextInput else { - gCurrentCandidateController?.visible = false + ctlCandidateCurrent?.visible = false return } client.setMarkedText( @@ -664,14 +664,14 @@ extension ctlInputMethod { return false }() - gCurrentCandidateController?.delegate = nil + ctlCandidateCurrent?.delegate = nil if useVerticalMode { - gCurrentCandidateController = .vertical + ctlCandidateCurrent = .vertical } else if mgrPrefs.useHorizontalCandidateList { - gCurrentCandidateController = .horizontal + ctlCandidateCurrent = .horizontal } else { - gCurrentCandidateController = .vertical + ctlCandidateCurrent = .vertical } // set the attributes for the candidate panel (which uses NSAttributedString) @@ -699,24 +699,24 @@ extension ctlInputMethod { return finalReturnFont } - gCurrentCandidateController?.keyLabelFont = labelFont( + ctlCandidateCurrent?.keyLabelFont = labelFont( name: mgrPrefs.candidateKeyLabelFontName, size: keyLabelSize) - gCurrentCandidateController?.candidateFont = candidateFont( + ctlCandidateCurrent?.candidateFont = candidateFont( name: mgrPrefs.candidateTextFontName, size: textSize) let candidateKeys = mgrPrefs.candidateKeys let keyLabels = candidateKeys.count > 4 ? Array(candidateKeys) : Array(mgrPrefs.defaultCandidateKeys) let keyLabelSuffix = state is InputState.AssociatedPhrases ? "^" : "" - gCurrentCandidateController?.keyLabels = keyLabels.map { + ctlCandidateCurrent?.keyLabels = keyLabels.map { CandidateKeyLabel(key: String($0), displayedText: String($0) + keyLabelSuffix) } - gCurrentCandidateController?.delegate = self - gCurrentCandidateController?.reloadData() + ctlCandidateCurrent?.delegate = self + ctlCandidateCurrent?.reloadData() currentCandidateClient = client - gCurrentCandidateController?.visible = true + ctlCandidateCurrent?.visible = true var lineHeightRect = NSMakeRect(0.0, 0.0, 16.0, 16.0) var cursor: Int = 0 @@ -735,13 +735,13 @@ extension ctlInputMethod { } if useVerticalMode { - gCurrentCandidateController?.set( + ctlCandidateCurrent?.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( + ctlCandidateCurrent?.set( windowTopLeftPoint: NSMakePoint( lineHeightRect.origin.x, lineHeightRect.origin.y - 4.0), bottomOutOfScreenAdjustmentHeight: lineHeightRect.size.height + 4.0) @@ -776,16 +776,16 @@ extension ctlInputMethod { // MARK: - extension ctlInputMethod: KeyHandlerDelegate { - func candidateController(for keyHandler: KeyHandler) -> Any { - gCurrentCandidateController ?? .vertical + func ctlCandidate(for keyHandler: KeyHandler) -> Any { + ctlCandidateCurrent ?? .vertical } func keyHandler( _ keyHandler: KeyHandler, didSelectCandidateAt index: Int, - candidateController controller: Any + ctlCandidate controller: Any ) { - if let controller = controller as? CandidateController { - self.candidateController(controller, didSelectCandidateAtIndex: UInt(index)) + if let controller = controller as? ctlCandidate { + self.ctlCandidate(controller, didSelectCandidateAtIndex: UInt(index)) } } @@ -815,8 +815,8 @@ extension ctlInputMethod: KeyHandlerDelegate { // MARK: - -extension ctlInputMethod: CandidateControllerDelegate { - func candidateCountForController(_ controller: CandidateController) -> UInt { +extension ctlInputMethod: ctlCandidateDelegate { + func candidateCountForController(_ controller: ctlCandidate) -> UInt { if let state = state as? InputState.ChoosingCandidate { return UInt(state.candidates.count) } else if let state = state as? InputState.AssociatedPhrases { @@ -825,7 +825,7 @@ extension ctlInputMethod: CandidateControllerDelegate { return 0 } - func candidateController(_ controller: CandidateController, candidateAtIndex index: UInt) + func ctlCandidate(_ controller: ctlCandidate, candidateAtIndex index: UInt) -> String { if let state = state as? InputState.ChoosingCandidate { @@ -836,9 +836,7 @@ extension ctlInputMethod: CandidateControllerDelegate { return "" } - func candidateController( - _ controller: CandidateController, didSelectCandidateAtIndex index: UInt - ) { + func ctlCandidate(_ controller: ctlCandidate, didSelectCandidateAtIndex index: UInt) { let client = currentCandidateClient if let state = state as? InputState.SymbolTable, diff --git a/Source/UI/CandidateUI/CandidateController.swift b/Source/UI/CandidateUI/ctlCandidate.swift similarity index 91% rename from Source/UI/CandidateUI/CandidateController.swift rename to Source/UI/CandidateUI/ctlCandidate.swift index 2ef1ce09..3ec8337f 100644 --- a/Source/UI/CandidateUI/CandidateController.swift +++ b/Source/UI/CandidateUI/ctlCandidate.swift @@ -38,18 +38,18 @@ public class CandidateKeyLabel: NSObject { } } -@objc(VTCandidateControllerDelegate) -public protocol CandidateControllerDelegate: AnyObject { - func candidateCountForController(_ controller: CandidateController) -> UInt - func candidateController(_ controller: CandidateController, candidateAtIndex index: UInt) +@objc(ctlCandidateDelegate) +public protocol ctlCandidateDelegate: AnyObject { + func candidateCountForController(_ controller: ctlCandidate) -> UInt + func ctlCandidate(_ controller: ctlCandidate, candidateAtIndex index: UInt) -> String - func candidateController( - _ controller: CandidateController, didSelectCandidateAtIndex index: UInt) + func ctlCandidate( + _ controller: ctlCandidate, didSelectCandidateAtIndex index: UInt) } -@objc(VTCandidateController) -public class CandidateController: NSWindowController { - @objc public weak var delegate: CandidateControllerDelegate? { +@objc(ctlCandidate) +public class ctlCandidate: NSWindowController { + @objc public weak var delegate: ctlCandidateDelegate? { didSet { reloadData() } diff --git a/Source/UI/CandidateUI/HorizontalCandidateController.swift b/Source/UI/CandidateUI/ctlCandidateHorizontal.swift similarity index 98% rename from Source/UI/CandidateUI/HorizontalCandidateController.swift rename to Source/UI/CandidateUI/ctlCandidateHorizontal.swift index d6f2be0a..027703e9 100644 --- a/Source/UI/CandidateUI/HorizontalCandidateController.swift +++ b/Source/UI/CandidateUI/ctlCandidateHorizontal.swift @@ -236,8 +236,7 @@ private class HorizontalCandidateView: NSView { } } -@objc(VTHorizontalCandidateController) -public class HorizontalCandidateController: CandidateController { +@objc public class ctlCandidateHorizontal: ctlCandidate { private var candidateView: HorizontalCandidateView private var prevPageButton: NSButton private var nextPageButton: NSButton @@ -379,7 +378,7 @@ public class HorizontalCandidateController: CandidateController { } } -extension HorizontalCandidateController { +extension ctlCandidateHorizontal { private var pageCount: UInt { guard let delegate = delegate else { @@ -402,7 +401,7 @@ extension HorizontalCandidateController { let begin = currentPage * keyLabelCount for index in begin.. Date: Sun, 3 Apr 2022 14:50:45 +0800 Subject: [PATCH 12/16] KeyHandler // Re-enable upstream symbol menu. - We changed the hotkey of libChewing-style symbol menu to Alt+` since it has serious compatibility issues with non-standard Cocoa apps like iTerm2, Microsoft Word 2019, WeChat, etc. --- .../Modules/ControllerModules/KeyHandler.mm | 57 +++++++++++-------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/Source/Modules/ControllerModules/KeyHandler.mm b/Source/Modules/ControllerModules/KeyHandler.mm index 0f638936..df512be9 100644 --- a/Source/Modules/ControllerModules/KeyHandler.mm +++ b/Source/Modules/ControllerModules/KeyHandler.mm @@ -638,30 +638,41 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; // MARK: Punctuation list if ([input isSymbolMenuPhysicalKey] && ![input isShiftHold]) { + if (![input isOptionHold]) + { + if (_languageModel->hasUnigramsForKey("_punctuation_list")) + { + if (_bpmfReadingBuffer->isEmpty()) + { + _builder->insertReadingAtCursor(string("_punctuation_list")); + NSString *poppedText = [self _popOverflowComposingTextAndWalk]; + InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; + inputting.poppedText = poppedText; + stateCallback(inputting); + InputStateChoosingCandidate *choosingCandidate = [self _buildCandidateState:inputting + useVerticalMode:input.useVerticalMode]; + stateCallback(choosingCandidate); + } + else + { // If there is still unfinished bpmf reading, ignore the punctuation + [IME prtDebugIntel:@"17446655"]; + errorCallback(); + } + return YES; + } + } + else + { + // 得在這裡先 commit buffer,不然會導致「在摁 ESC 離開符號選單時會重複輸入上一次的組字區的內容」的不當行為。 + // 於是這裡用「模擬一次 Enter 鍵的操作」使其代為執行這個 commit buffer 的動作。 + [self _handleEnterWithState:state stateCallback:stateCallback errorCallback:errorCallback]; - // 得在這裡先 commit buffer,不然會導致「在摁 ESC 離開符號選單時會重複輸入上一次的組字區的內容」的不當行為。 - // 於是這裡用「模擬一次 Enter 鍵的操作」使其代為執行這個 commit buffer 的動作。 - [self _handleEnterWithState:state stateCallback:stateCallback errorCallback:errorCallback]; - - SymbolNode *root = [SymbolNode root]; - InputStateSymbolTable *symbolState = [[InputStateSymbolTable alloc] initWithNode:root - useVerticalMode:input.useVerticalMode]; - stateCallback(symbolState); - return YES; - // if (_languageModel->hasUnigramsForKey("_punctuation_list"))) { - // if (_bpmfReadingBuffer->isEmpty()) { - // _builder->insertReadingAtCursor(string("_punctuation_list")); - // NSString *poppedText = [self _popOverflowComposingTextAndWalk]; - // InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; - // inputting.poppedText = poppedText; - // stateCallback(inputting); - // InputStateChoosingCandidate *choosingCandidate = [self _buildCandidateState:inputting - // useVerticalMode:input.useVerticalMode]; stateCallback(choosingCandidate); - // } else { // If there is still unfinished bpmf reading, ignore the punctuation - // errorCallback(); - // } - // return YES; - // } + SymbolNode *root = [SymbolNode root]; + InputStateSymbolTable *symbolState = [[InputStateSymbolTable alloc] initWithNode:root + useVerticalMode:input.useVerticalMode]; + stateCallback(symbolState); + return YES; + } } // MARK: Punctuation From 3240ec1567e18a4f5655ffb841ab54c1b51ae630 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Mon, 4 Apr 2022 13:43:08 +0800 Subject: [PATCH 13/16] ctlIME // Move areWeUsingOurOwnPhraseEditor to IME module. --- Source/Modules/ControllerModules/KeyHandler.mm | 5 ++--- Source/Modules/IME.swift | 3 +++ Source/Modules/IMEModules/ctlInputMethod.swift | 10 ++-------- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/Source/Modules/ControllerModules/KeyHandler.mm b/Source/Modules/ControllerModules/KeyHandler.mm index df512be9..8b912ea6 100644 --- a/Source/Modules/ControllerModules/KeyHandler.mm +++ b/Source/Modules/ControllerModules/KeyHandler.mm @@ -1089,9 +1089,8 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; return NO; NSArray *readings = [self _currentReadings]; - NSString *composingBuffer = (ctlInputMethod.areWeUsingOurOwnPhraseEditor) - ? [readings componentsJoinedByString:@"-"] - : [readings componentsJoinedByString:@" "]; + NSString *composingBuffer = (IME.areWeUsingOurOwnPhraseEditor) ? [readings componentsJoinedByString:@"-"] + : [readings componentsJoinedByString:@" "]; [self clear]; diff --git a/Source/Modules/IME.swift b/Source/Modules/IME.swift index 4fd3a127..72d69719 100644 --- a/Source/Modules/IME.swift +++ b/Source/Modules/IME.swift @@ -28,6 +28,9 @@ import Cocoa static let dlgOpenPath = NSOpenPanel() + // MARK: - 開關判定當前應用究竟是? + @objc static var areWeUsingOurOwnPhraseEditor: Bool = false + // MARK: - Print debug information to the console. @objc static func prtDebugIntel(_ strPrint: String) { if mgrPrefs.isDebugModeEnabled { diff --git a/Source/Modules/IMEModules/ctlInputMethod.swift b/Source/Modules/IMEModules/ctlInputMethod.swift index 73f2c310..09ae6e20 100644 --- a/Source/Modules/IMEModules/ctlInputMethod.swift +++ b/Source/Modules/IMEModules/ctlInputMethod.swift @@ -271,9 +271,9 @@ class ctlInputMethod: IMKInputController { if (client as? IMKTextInput)?.bundleIdentifier() == "org.atelierInmu.vChewing.vChewingPhraseEditor" { - ctlInputMethod.areWeUsingOurOwnPhraseEditor = true + IME.areWeUsingOurOwnPhraseEditor = true } else { - ctlInputMethod.areWeUsingOurOwnPhraseEditor = false + IME.areWeUsingOurOwnPhraseEditor = false } let input = keyParser(event: event, isVerticalMode: useVerticalMode) @@ -767,12 +767,6 @@ extension ctlInputMethod { } } -// MARK: - 開關判定當前應用究竟是? - -@objc extension ctlInputMethod { - @objc static var areWeUsingOurOwnPhraseEditor: Bool = false -} - // MARK: - extension ctlInputMethod: KeyHandlerDelegate { From c6220d1e1615b7b4f8c2efa8ccccb913de33506a Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Mon, 4 Apr 2022 13:52:35 +0800 Subject: [PATCH 14/16] ctlIME // Extract all menu-related contents to a standalone file. - Use a standalone swift file to manage the composition of the menu itself and its subcommands. --- Source/Modules/IME.swift | 29 ++ .../Modules/IMEModules/ctlInputMethod.swift | 273 ----------------- .../IMEModules/ctlInputMethod_Menu.swift | 282 ++++++++++++++++++ vChewing.xcodeproj/project.pbxproj | 4 + 4 files changed, 315 insertions(+), 273 deletions(-) create mode 100644 Source/Modules/IMEModules/ctlInputMethod_Menu.swift diff --git a/Source/Modules/IME.swift b/Source/Modules/IME.swift index 72d69719..2e60ec79 100644 --- a/Source/Modules/IME.swift +++ b/Source/Modules/IME.swift @@ -31,6 +31,11 @@ import Cocoa // MARK: - 開關判定當前應用究竟是? @objc static var areWeUsingOurOwnPhraseEditor: Bool = false + // MARK: - 自 ctlInputMethod 讀取當前輸入法的簡繁體模式 + static func getInputMode() -> InputMode { + return ctlInputMethod.currentKeyHandler.inputMode + } + // MARK: - Print debug information to the console. @objc static func prtDebugIntel(_ strPrint: String) { if mgrPrefs.isDebugModeEnabled { @@ -76,6 +81,30 @@ import Cocoa return false } + // MARK: - Open a phrase data file. + static func openPhraseFile(userFileAt path: String) { + func checkIfUserFilesExist() -> Bool { + if !mgrLangModel.checkIfUserLanguageModelFilesExist() { + let content = String( + format: NSLocalizedString( + "Please check the permission at \"%@\".", comment: ""), + mgrLangModel.dataFolderPath(isDefaultFolder: false)) + ctlNonModalAlertWindow.shared.show( + title: NSLocalizedString("Unable to create the user phrase file.", comment: ""), + content: content, confirmButtonTitle: NSLocalizedString("OK", comment: ""), + cancelButtonTitle: nil, cancelAsDefault: false, delegate: nil) + NSApp.setActivationPolicy(.accessory) + return false + } + return true + } + + if !checkIfUserFilesExist() { + return + } + NSWorkspace.shared.openFile(path, withApplication: "vChewingPhraseEditor") + } + // MARK: - Trash a file if it exists. @discardableResult static func trashTargetIfExists(_ path: String) -> Bool { do { diff --git a/Source/Modules/IMEModules/ctlInputMethod.swift b/Source/Modules/IMEModules/ctlInputMethod.swift index 09ae6e20..ef75f81d 100644 --- a/Source/Modules/IMEModules/ctlInputMethod.swift +++ b/Source/Modules/IMEModules/ctlInputMethod.swift @@ -27,12 +27,6 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import Cocoa import InputMethodKit -extension Bool { - fileprivate var state: NSControl.StateValue { - self ? .on : .off - } -} - private let kMinKeyLabelSize: CGFloat = 10 private var ctlCandidateCurrent: ctlCandidate? @@ -82,118 +76,6 @@ class ctlInputMethod: IMKInputController { keyHandler.delegate = self } - override func menu() -> NSMenu! { - let optionKeyPressed = NSEvent.modifierFlags.contains(.option) - - let menu = NSMenu(title: "Input Method Menu") - - let useSCPCTypingModeItem = menu.addItem( - withTitle: NSLocalizedString("Per-Char Select Mode", comment: ""), - action: #selector(toggleSCPCTypingMode(_:)), keyEquivalent: "P") - useSCPCTypingModeItem.keyEquivalentModifierMask = [.command, .control] - useSCPCTypingModeItem.state = mgrPrefs.useSCPCTypingMode.state - - let useCNS11643SupportItem = menu.addItem( - withTitle: NSLocalizedString("CNS11643 Mode", comment: ""), - action: #selector(toggleCNS11643Enabled(_:)), keyEquivalent: "L") - useCNS11643SupportItem.keyEquivalentModifierMask = [.command, .control] - useCNS11643SupportItem.state = mgrPrefs.cns11643Enabled.state - - if keyHandler.inputMode == InputMode.imeModeCHT { - let chineseConversionItem = menu.addItem( - withTitle: NSLocalizedString("Force KangXi Writing", comment: ""), - action: #selector(toggleChineseConverter(_:)), keyEquivalent: "K") - chineseConversionItem.keyEquivalentModifierMask = [.command, .control] - chineseConversionItem.state = mgrPrefs.chineseConversionEnabled.state - - let shiftJISConversionItem = menu.addItem( - withTitle: NSLocalizedString("JIS Shinjitai Output", comment: ""), - action: #selector(toggleShiftJISShinjitaiOutput(_:)), keyEquivalent: "J") - shiftJISConversionItem.keyEquivalentModifierMask = [.command, .control] - shiftJISConversionItem.state = mgrPrefs.shiftJISShinjitaiOutputEnabled.state - } - - let halfWidthPunctuationItem = menu.addItem( - withTitle: NSLocalizedString("Half-Width Punctuation Mode", comment: ""), - action: #selector(toggleHalfWidthPunctuation(_:)), keyEquivalent: "H") - halfWidthPunctuationItem.keyEquivalentModifierMask = [.command, .control] - halfWidthPunctuationItem.state = mgrPrefs.halfWidthPunctuationEnabled.state - - let userAssociatedPhrasesItem = menu.addItem( - withTitle: NSLocalizedString("Per-Char Associated Phrases", comment: ""), - action: #selector(toggleAssociatedPhrasesEnabled(_:)), keyEquivalent: "O") - userAssociatedPhrasesItem.keyEquivalentModifierMask = [.command, .control] - userAssociatedPhrasesItem.state = mgrPrefs.associatedPhrasesEnabled.state - - if optionKeyPressed { - let phaseReplacementItem = menu.addItem( - withTitle: NSLocalizedString("Use Phrase Replacement", comment: ""), - action: #selector(togglePhraseReplacement(_:)), keyEquivalent: "") - phaseReplacementItem.state = mgrPrefs.phraseReplacementEnabled.state - - let toggleSymbolInputItem = menu.addItem( - withTitle: NSLocalizedString("Symbol & Emoji Input", comment: ""), - action: #selector(toggleSymbolEnabled(_:)), keyEquivalent: "") - toggleSymbolInputItem.state = mgrPrefs.symbolInputEnabled.state - } - - menu.addItem(NSMenuItem.separator()) // --------------------- - - menu.addItem( - withTitle: NSLocalizedString("Open User Data Folder", comment: ""), - action: #selector(openUserDataFolder(_:)), keyEquivalent: "") - menu.addItem( - withTitle: NSLocalizedString("Edit User Phrases…", comment: ""), - action: #selector(openUserPhrases(_:)), keyEquivalent: "") - - if optionKeyPressed { - menu.addItem( - withTitle: NSLocalizedString("Edit Excluded Phrases…", comment: ""), - action: #selector(openExcludedPhrases(_:)), keyEquivalent: "") - menu.addItem( - withTitle: NSLocalizedString("Edit Phrase Replacement Table…", comment: ""), - action: #selector(openPhraseReplacement(_:)), keyEquivalent: "") - menu.addItem( - withTitle: NSLocalizedString("Edit Associated Phrases…", comment: ""), - action: #selector(openAssociatedPhrases(_:)), keyEquivalent: "") - menu.addItem( - withTitle: NSLocalizedString("Edit User Symbol & Emoji Data…", comment: ""), - action: #selector(openUserSymbols(_:)), keyEquivalent: "") - } - - if optionKeyPressed || !mgrPrefs.shouldAutoReloadUserDataFiles { - menu.addItem( - withTitle: NSLocalizedString("Reload User Phrases", comment: ""), - action: #selector(reloadUserPhrases(_:)), keyEquivalent: "") - } - - menu.addItem(NSMenuItem.separator()) // --------------------- - - menu.addItem( - withTitle: NSLocalizedString("vChewing Preferences…", comment: ""), - action: #selector(showPreferences(_:)), keyEquivalent: "") - if !optionKeyPressed { - menu.addItem( - withTitle: NSLocalizedString("Check for Updates…", comment: ""), - action: #selector(checkForUpdate(_:)), keyEquivalent: "") - } - menu.addItem( - withTitle: NSLocalizedString("Reboot vChewing…", comment: ""), - action: #selector(selfTerminate(_:)), keyEquivalent: "") - menu.addItem( - withTitle: NSLocalizedString("About vChewing…", comment: ""), - action: #selector(showAbout(_:)), keyEquivalent: "") - if optionKeyPressed { - menu.addItem( - withTitle: NSLocalizedString("Uninstall vChewing…", comment: ""), - action: #selector(selfUninstall(_:)), keyEquivalent: "") - } - - // NSMenu 會阻止任何 modified key 相關的訊號傳回輸入法,所以咱們在此重設鍵盤佈局 - setKeyLayout() - return menu - } - // MARK: - IMKStateSetting protocol methods override func activateServer(_ client: Any!) { @@ -285,161 +167,6 @@ class ctlInputMethod: IMKInputController { } return result } - - // MARK: - Menu Items - - @objc override func showPreferences(_ sender: Any?) { - (NSApp.delegate as? AppDelegate)?.showPreferences() - NSApp.activate(ignoringOtherApps: true) - } - - @objc func toggleSCPCTypingMode(_ sender: Any?) { - NotifierController.notify( - message: String( - format: "%@%@%@", NSLocalizedString("Per-Char Select Mode", comment: ""), "\n", - mgrPrefs.toggleSCPCTypingModeEnabled() - ? NSLocalizedString("NotificationSwitchON", comment: "") - : NSLocalizedString("NotificationSwitchOFF", comment: ""))) - } - - @objc func toggleChineseConverter(_ sender: Any?) { - NotifierController.notify( - message: String( - format: "%@%@%@", NSLocalizedString("Force KangXi Writing", comment: ""), "\n", - mgrPrefs.toggleChineseConversionEnabled() - ? NSLocalizedString("NotificationSwitchON", comment: "") - : NSLocalizedString("NotificationSwitchOFF", comment: ""))) - } - - @objc func toggleShiftJISShinjitaiOutput(_ sender: Any?) { - NotifierController.notify( - message: String( - format: "%@%@%@", NSLocalizedString("JIS Shinjitai Output", comment: ""), "\n", - mgrPrefs.toggleShiftJISShinjitaiOutputEnabled() - ? NSLocalizedString("NotificationSwitchON", comment: "") - : NSLocalizedString("NotificationSwitchOFF", comment: ""))) - } - - @objc func toggleHalfWidthPunctuation(_ sender: Any?) { - NotifierController.notify( - message: String( - format: "%@%@%@", NSLocalizedString("Half-Width Punctuation Mode", comment: ""), - "\n", - mgrPrefs.toggleHalfWidthPunctuationEnabled() - ? NSLocalizedString("NotificationSwitchON", comment: "") - : NSLocalizedString("NotificationSwitchOFF", comment: ""))) - } - - @objc func toggleCNS11643Enabled(_ sender: Any?) { - NotifierController.notify( - message: String( - format: "%@%@%@", NSLocalizedString("CNS11643 Mode", comment: ""), "\n", - mgrPrefs.toggleCNS11643Enabled() - ? NSLocalizedString("NotificationSwitchON", comment: "") - : NSLocalizedString("NotificationSwitchOFF", comment: ""))) - } - - @objc func toggleSymbolEnabled(_ sender: Any?) { - NotifierController.notify( - message: String( - format: "%@%@%@", NSLocalizedString("Symbol & Emoji Input", comment: ""), "\n", - mgrPrefs.toggleSymbolInputEnabled() - ? NSLocalizedString("NotificationSwitchON", comment: "") - : NSLocalizedString("NotificationSwitchOFF", comment: ""))) - } - - @objc func toggleAssociatedPhrasesEnabled(_ sender: Any?) { - NotifierController.notify( - message: String( - format: "%@%@%@", NSLocalizedString("Per-Char Associated Phrases", comment: ""), - "\n", - mgrPrefs.toggleAssociatedPhrasesEnabled() - ? NSLocalizedString("NotificationSwitchON", comment: "") - : NSLocalizedString("NotificationSwitchOFF", comment: ""))) - } - - @objc func togglePhraseReplacement(_ sender: Any?) { - NotifierController.notify( - message: String( - format: "%@%@%@", NSLocalizedString("Use Phrase Replacement", comment: ""), "\n", - mgrPrefs.togglePhraseReplacementEnabled() - ? NSLocalizedString("NotificationSwitchON", comment: "") - : NSLocalizedString("NotificationSwitchOFF", comment: ""))) - } - - @objc func selfUninstall(_ sender: Any?) { - (NSApp.delegate as? AppDelegate)?.selfUninstall() - } - - @objc func selfTerminate(_ sender: Any?) { - NSApp.terminate(nil) - } - - @objc func checkForUpdate(_ sender: Any?) { - (NSApp.delegate as? AppDelegate)?.checkForUpdate(forced: true) - } - - private func open(userFileAt path: String) { - func checkIfUserFilesExist() -> Bool { - if !mgrLangModel.checkIfUserLanguageModelFilesExist() { - let content = String( - format: NSLocalizedString( - "Please check the permission at \"%@\".", comment: ""), - mgrLangModel.dataFolderPath(isDefaultFolder: false)) - ctlNonModalAlertWindow.shared.show( - title: NSLocalizedString("Unable to create the user phrase file.", comment: ""), - content: content, confirmButtonTitle: NSLocalizedString("OK", comment: ""), - cancelButtonTitle: nil, cancelAsDefault: false, delegate: nil) - NSApp.setActivationPolicy(.accessory) - return false - } - return true - } - - if !checkIfUserFilesExist() { - return - } - NSWorkspace.shared.openFile(path, withApplication: "vChewingPhraseEditor") - } - - @objc func openUserPhrases(_ sender: Any?) { - open(userFileAt: mgrLangModel.userPhrasesDataPath(keyHandler.inputMode)) - } - - @objc func openUserDataFolder(_ sender: Any?) { - if !mgrLangModel.checkIfUserDataFolderExists() { - return - } - NSWorkspace.shared.openFile( - mgrLangModel.dataFolderPath(isDefaultFolder: false), withApplication: "Finder") - } - - @objc func openExcludedPhrases(_ sender: Any?) { - open(userFileAt: mgrLangModel.excludedPhrasesDataPath(keyHandler.inputMode)) - } - - @objc func openUserSymbols(_ sender: Any?) { - open(userFileAt: mgrLangModel.userSymbolDataPath(keyHandler.inputMode)) - } - - @objc func openPhraseReplacement(_ sender: Any?) { - open(userFileAt: mgrLangModel.phraseReplacementDataPath(keyHandler.inputMode)) - } - - @objc func openAssociatedPhrases(_ sender: Any?) { - open(userFileAt: mgrLangModel.userAssociatedPhrasesDataPath(keyHandler.inputMode)) - } - - @objc func reloadUserPhrases(_ sender: Any?) { - mgrLangModel.loadUserPhrases() - mgrLangModel.loadUserPhraseReplacement() - } - - @objc func showAbout(_ sender: Any?) { - (NSApp.delegate as? AppDelegate)?.showAbout() - NSApp.activate(ignoringOtherApps: true) - } - } // MARK: - State Handling diff --git a/Source/Modules/IMEModules/ctlInputMethod_Menu.swift b/Source/Modules/IMEModules/ctlInputMethod_Menu.swift new file mode 100644 index 00000000..a9152e56 --- /dev/null +++ b/Source/Modules/IMEModules/ctlInputMethod_Menu.swift @@ -0,0 +1,282 @@ +// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). +// All possible vChewing-specific modifications are of: +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). +/* +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: + +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +2. No trademark license is granted to use the trade names, trademarks, service +marks, or product names of Contributor, except as required to fulfill notice +requirements above. + +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 + +extension Bool { + fileprivate var state: NSControl.StateValue { + self ? .on : .off + } +} + +// MARK: - IME Menu Manager +// 因為選單部分的內容又臭又長,所以就單獨拉到一個檔案內管理了。 + +extension ctlInputMethod { + override func menu() -> NSMenu! { + let optionKeyPressed = NSEvent.modifierFlags.contains(.option) + + let menu = NSMenu(title: "Input Method Menu") + + let useSCPCTypingModeItem = menu.addItem( + withTitle: NSLocalizedString("Per-Char Select Mode", comment: ""), + action: #selector(toggleSCPCTypingMode(_:)), keyEquivalent: "P") + useSCPCTypingModeItem.keyEquivalentModifierMask = [.command, .control] + useSCPCTypingModeItem.state = mgrPrefs.useSCPCTypingMode.state + + let userAssociatedPhrasesItem = menu.addItem( + withTitle: NSLocalizedString("Per-Char Associated Phrases", comment: ""), + action: #selector(toggleAssociatedPhrasesEnabled(_:)), keyEquivalent: "O") + userAssociatedPhrasesItem.keyEquivalentModifierMask = [.command, .control] + userAssociatedPhrasesItem.state = mgrPrefs.associatedPhrasesEnabled.state + + let useCNS11643SupportItem = menu.addItem( + withTitle: NSLocalizedString("CNS11643 Mode", comment: ""), + action: #selector(toggleCNS11643Enabled(_:)), keyEquivalent: "L") + useCNS11643SupportItem.keyEquivalentModifierMask = [.command, .control] + useCNS11643SupportItem.state = mgrPrefs.cns11643Enabled.state + + if IME.getInputMode() == InputMode.imeModeCHT { + let chineseConversionItem = menu.addItem( + withTitle: NSLocalizedString("Force KangXi Writing", comment: ""), + action: #selector(toggleChineseConverter(_:)), keyEquivalent: "K") + chineseConversionItem.keyEquivalentModifierMask = [.command, .control] + chineseConversionItem.state = mgrPrefs.chineseConversionEnabled.state + + let shiftJISConversionItem = menu.addItem( + withTitle: NSLocalizedString("JIS Shinjitai Output", comment: ""), + action: #selector(toggleShiftJISShinjitaiOutput(_:)), keyEquivalent: "J") + shiftJISConversionItem.keyEquivalentModifierMask = [.command, .control] + shiftJISConversionItem.state = mgrPrefs.shiftJISShinjitaiOutputEnabled.state + } + + let halfWidthPunctuationItem = menu.addItem( + withTitle: NSLocalizedString("Half-Width Punctuation Mode", comment: ""), + action: #selector(toggleHalfWidthPunctuation(_:)), keyEquivalent: "H") + halfWidthPunctuationItem.keyEquivalentModifierMask = [.command, .control] + halfWidthPunctuationItem.state = mgrPrefs.halfWidthPunctuationEnabled.state + + if optionKeyPressed { + let phaseReplacementItem = menu.addItem( + withTitle: NSLocalizedString("Use Phrase Replacement", comment: ""), + action: #selector(togglePhraseReplacement(_:)), keyEquivalent: "") + phaseReplacementItem.state = mgrPrefs.phraseReplacementEnabled.state + + let toggleSymbolInputItem = menu.addItem( + withTitle: NSLocalizedString("Symbol & Emoji Input", comment: ""), + action: #selector(toggleSymbolEnabled(_:)), keyEquivalent: "") + toggleSymbolInputItem.state = mgrPrefs.symbolInputEnabled.state + } + + menu.addItem(NSMenuItem.separator()) // --------------------- + + menu.addItem( + withTitle: NSLocalizedString("Open User Data Folder", comment: ""), + action: #selector(openUserDataFolder(_:)), keyEquivalent: "") + menu.addItem( + withTitle: NSLocalizedString("Edit User Phrases…", comment: ""), + action: #selector(openUserPhrases(_:)), keyEquivalent: "") + + if optionKeyPressed { + menu.addItem( + withTitle: NSLocalizedString("Edit Excluded Phrases…", comment: ""), + action: #selector(openExcludedPhrases(_:)), keyEquivalent: "") + menu.addItem( + withTitle: NSLocalizedString("Edit Phrase Replacement Table…", comment: ""), + action: #selector(openPhraseReplacement(_:)), keyEquivalent: "") + menu.addItem( + withTitle: NSLocalizedString("Edit Associated Phrases…", comment: ""), + action: #selector(openAssociatedPhrases(_:)), keyEquivalent: "") + menu.addItem( + withTitle: NSLocalizedString("Edit User Symbol & Emoji Data…", comment: ""), + action: #selector(openUserSymbols(_:)), keyEquivalent: "") + } + + if optionKeyPressed || !mgrPrefs.shouldAutoReloadUserDataFiles { + menu.addItem( + withTitle: NSLocalizedString("Reload User Phrases", comment: ""), + action: #selector(reloadUserPhrases(_:)), keyEquivalent: "") + } + + menu.addItem(NSMenuItem.separator()) // --------------------- + + menu.addItem( + withTitle: NSLocalizedString("vChewing Preferences…", comment: ""), + action: #selector(showPreferences(_:)), keyEquivalent: "") + if !optionKeyPressed { + menu.addItem( + withTitle: NSLocalizedString("Check for Updates…", comment: ""), + action: #selector(checkForUpdate(_:)), keyEquivalent: "") + } + menu.addItem( + withTitle: NSLocalizedString("Reboot vChewing…", comment: ""), + action: #selector(selfTerminate(_:)), keyEquivalent: "") + menu.addItem( + withTitle: NSLocalizedString("About vChewing…", comment: ""), + action: #selector(showAbout(_:)), keyEquivalent: "") + if optionKeyPressed { + menu.addItem( + withTitle: NSLocalizedString("Uninstall vChewing…", comment: ""), + action: #selector(selfUninstall(_:)), keyEquivalent: "") + } + + // NSMenu 會阻止任何 modified key 相關的訊號傳回輸入法,所以咱們在此重設鍵盤佈局 + setKeyLayout() + + return menu + } + + // MARK: - IME Menu Items + + @objc override func showPreferences(_ sender: Any?) { + (NSApp.delegate as? AppDelegate)?.showPreferences() + NSApp.activate(ignoringOtherApps: true) + } + + @objc func toggleSCPCTypingMode(_ sender: Any?) { + NotifierController.notify( + message: String( + format: "%@%@%@", NSLocalizedString("Per-Char Select Mode", comment: ""), "\n", + mgrPrefs.toggleSCPCTypingModeEnabled() + ? NSLocalizedString("NotificationSwitchON", comment: "") + : NSLocalizedString("NotificationSwitchOFF", comment: ""))) + } + + @objc func toggleChineseConverter(_ sender: Any?) { + NotifierController.notify( + message: String( + format: "%@%@%@", NSLocalizedString("Force KangXi Writing", comment: ""), "\n", + mgrPrefs.toggleChineseConversionEnabled() + ? NSLocalizedString("NotificationSwitchON", comment: "") + : NSLocalizedString("NotificationSwitchOFF", comment: ""))) + } + + @objc func toggleShiftJISShinjitaiOutput(_ sender: Any?) { + NotifierController.notify( + message: String( + format: "%@%@%@", NSLocalizedString("JIS Shinjitai Output", comment: ""), "\n", + mgrPrefs.toggleShiftJISShinjitaiOutputEnabled() + ? NSLocalizedString("NotificationSwitchON", comment: "") + : NSLocalizedString("NotificationSwitchOFF", comment: ""))) + } + + @objc func toggleHalfWidthPunctuation(_ sender: Any?) { + NotifierController.notify( + message: String( + format: "%@%@%@", NSLocalizedString("Half-Width Punctuation Mode", comment: ""), + "\n", + mgrPrefs.toggleHalfWidthPunctuationEnabled() + ? NSLocalizedString("NotificationSwitchON", comment: "") + : NSLocalizedString("NotificationSwitchOFF", comment: ""))) + } + + @objc func toggleCNS11643Enabled(_ sender: Any?) { + NotifierController.notify( + message: String( + format: "%@%@%@", NSLocalizedString("CNS11643 Mode", comment: ""), "\n", + mgrPrefs.toggleCNS11643Enabled() + ? NSLocalizedString("NotificationSwitchON", comment: "") + : NSLocalizedString("NotificationSwitchOFF", comment: ""))) + } + + @objc func toggleSymbolEnabled(_ sender: Any?) { + NotifierController.notify( + message: String( + format: "%@%@%@", NSLocalizedString("Symbol & Emoji Input", comment: ""), "\n", + mgrPrefs.toggleSymbolInputEnabled() + ? NSLocalizedString("NotificationSwitchON", comment: "") + : NSLocalizedString("NotificationSwitchOFF", comment: ""))) + } + + @objc func toggleAssociatedPhrasesEnabled(_ sender: Any?) { + NotifierController.notify( + message: String( + format: "%@%@%@", NSLocalizedString("Per-Char Associated Phrases", comment: ""), + "\n", + mgrPrefs.toggleAssociatedPhrasesEnabled() + ? NSLocalizedString("NotificationSwitchON", comment: "") + : NSLocalizedString("NotificationSwitchOFF", comment: ""))) + } + + @objc func togglePhraseReplacement(_ sender: Any?) { + NotifierController.notify( + message: String( + format: "%@%@%@", NSLocalizedString("Use Phrase Replacement", comment: ""), "\n", + mgrPrefs.togglePhraseReplacementEnabled() + ? NSLocalizedString("NotificationSwitchON", comment: "") + : NSLocalizedString("NotificationSwitchOFF", comment: ""))) + } + + @objc func selfUninstall(_ sender: Any?) { + (NSApp.delegate as? AppDelegate)?.selfUninstall() + } + + @objc func selfTerminate(_ sender: Any?) { + NSApp.terminate(nil) + } + + @objc func checkForUpdate(_ sender: Any?) { + (NSApp.delegate as? AppDelegate)?.checkForUpdate(forced: true) + } + + @objc func openUserPhrases(_ sender: Any?) { + IME.openPhraseFile(userFileAt: mgrLangModel.userPhrasesDataPath(IME.getInputMode())) + } + + @objc func openUserDataFolder(_ sender: Any?) { + if !mgrLangModel.checkIfUserDataFolderExists() { + return + } + NSWorkspace.shared.openFile( + mgrLangModel.dataFolderPath(isDefaultFolder: false), withApplication: "Finder") + } + + @objc func openExcludedPhrases(_ sender: Any?) { + IME.openPhraseFile(userFileAt: mgrLangModel.excludedPhrasesDataPath(IME.getInputMode())) + } + + @objc func openUserSymbols(_ sender: Any?) { + IME.openPhraseFile(userFileAt: mgrLangModel.userSymbolDataPath(IME.getInputMode())) + } + + @objc func openPhraseReplacement(_ sender: Any?) { + IME.openPhraseFile(userFileAt: mgrLangModel.phraseReplacementDataPath(IME.getInputMode())) + } + + @objc func openAssociatedPhrases(_ sender: Any?) { + IME.openPhraseFile(userFileAt: mgrLangModel.userAssociatedPhrasesDataPath(IME.getInputMode())) + } + + @objc func reloadUserPhrases(_ sender: Any?) { + mgrLangModel.loadUserPhrases() + mgrLangModel.loadUserPhraseReplacement() + } + + @objc func showAbout(_ sender: Any?) { + (NSApp.delegate as? AppDelegate)?.showAbout() + NSApp.activate(ignoringOtherApps: true) + } +} diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index cc2cfdac..7524cb15 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -31,6 +31,7 @@ 5B73FB5E27B2BE1300E9BF49 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5B73FB6027B2BE1300E9BF49 /* InfoPlist.strings */; }; 5B7BC4B027AFFBE800F66C24 /* frmPrefWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5B7BC4AE27AFFBE800F66C24 /* frmPrefWindow.xib */; }; 5BAD0CD527D701F6003D127F /* vChewingKeyLayout.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 5B30F11227BA568800484E24 /* vChewingKeyLayout.bundle */; }; + 5BB802DA27FABA8300CF1C19 /* ctlInputMethod_Menu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BB802D927FABA8300CF1C19 /* ctlInputMethod_Menu.swift */; }; 5BBBB75F27AED54C0023B93A /* Beep.m4a in Resources */ = {isa = PBXBuildFile; fileRef = 5BBBB75D27AED54C0023B93A /* Beep.m4a */; }; 5BBBB76027AED54C0023B93A /* Fart.m4a in Resources */ = {isa = PBXBuildFile; fileRef = 5BBBB75E27AED54C0023B93A /* Fart.m4a */; }; 5BBBB76B27AED5DB0023B93A /* frmNonModalAlertWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5BBBB76527AED5DB0023B93A /* frmNonModalAlertWindow.xib */; }; @@ -187,6 +188,7 @@ 5B7BC4AF27AFFBE800F66C24 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Source/WindowNIBs/Base.lproj/frmPrefWindow.xib; sourceTree = ""; }; 5B7BC4B227AFFC0B00F66C24 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = Source/WindowNIBs/en.lproj/frmPrefWindow.strings; sourceTree = ""; }; 5B8F43ED27C9BC220069AC27 /* SymbolLM.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SymbolLM.h; sourceTree = ""; }; + 5BB802D927FABA8300CF1C19 /* ctlInputMethod_Menu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ctlInputMethod_Menu.swift; sourceTree = ""; }; 5BBBB75D27AED54C0023B93A /* Beep.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = Beep.m4a; sourceTree = ""; }; 5BBBB75E27AED54C0023B93A /* Fart.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = Fart.m4a; sourceTree = ""; }; 5BBBB76627AED5DB0023B93A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/frmNonModalAlertWindow.xib; sourceTree = ""; }; @@ -409,6 +411,7 @@ isa = PBXGroup; children = ( D4A13D5927A59D5C003BE359 /* ctlInputMethod.swift */, + 5BB802D927FABA8300CF1C19 /* ctlInputMethod_Menu.swift */, 5B62A33127AE792F00A19448 /* InputSourceHelper.swift */, 5B62A33527AE795800A19448 /* mgrPrefs.swift */, ); @@ -990,6 +993,7 @@ 5BF8423127BAA942008E7E4C /* vChewingKanjiConverter.swift in Sources */, 5B62A34627AE7CD900A19448 /* ctlCandidateHorizontal.swift in Sources */, 5B62A34727AE7CD900A19448 /* ctlCandidate.swift in Sources */, + 5BB802DA27FABA8300CF1C19 /* ctlInputMethod_Menu.swift in Sources */, D41355DE278EA3ED005E5CBD /* UserPhrasesLM.mm in Sources */, 6ACC3D3F27914F2400F1B140 /* KeyValueBlobReader.cpp in Sources */, D41355D8278D74B5005E5CBD /* mgrLangModel.mm in Sources */, From d94eb1403f0ba2ff92745538eb86750eb1f809e6 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Mon, 4 Apr 2022 11:07:50 +0800 Subject: [PATCH 15/16] Update Data - 20220404 --- Source/Data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Data b/Source/Data index bc331e0d..2c734867 160000 --- a/Source/Data +++ b/Source/Data @@ -1 +1 @@ -Subproject commit bc331e0dd76e4888ac2ef1d945b191994b86fbf4 +Subproject commit 2c734867fe31b592c69a5b8f30008c4902800233 From a1b906d192c201db31071507b0eaa930e22f8867 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Mon, 4 Apr 2022 14:59:57 +0800 Subject: [PATCH 16/16] Bump version to 1.4.7 Build 1948. --- Update-Info.plist | 4 ++-- vChewing.pkgproj | 2 +- vChewing.xcodeproj/project.pbxproj | 24 ++++++++++++------------ 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Update-Info.plist b/Update-Info.plist index 15108072..bfaf39be 100644 --- a/Update-Info.plist +++ b/Update-Info.plist @@ -7,8 +7,8 @@ UpdateInfoSite https://gitee.com/vChewing/vChewing-macOS/releases CFBundleVersion - 1947 + 1948 CFBundleShortVersionString - 1.4.6 + 1.4.7 diff --git a/vChewing.pkgproj b/vChewing.pkgproj index 3ef8ad4e..2d25086f 100644 --- a/vChewing.pkgproj +++ b/vChewing.pkgproj @@ -726,7 +726,7 @@ USE_HFS+_COMPRESSION VERSION - 1.4.6 + 1.4.7 TYPE 0 diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index 7524cb15..1d4d13f0 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -1191,7 +1191,7 @@ CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1947; + CURRENT_PROJECT_VERSION = 1948; DEBUG_INFORMATION_FORMAT = dwarf; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; @@ -1214,7 +1214,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.11.5; - MARKETING_VERSION = 1.4.6; + MARKETING_VERSION = 1.4.7; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.vChewing.vChewingPhraseEditor; @@ -1247,7 +1247,7 @@ CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1947; + CURRENT_PROJECT_VERSION = 1948; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -1266,7 +1266,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.11.5; - MARKETING_VERSION = 1.4.6; + MARKETING_VERSION = 1.4.7; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.vChewing.vChewingPhraseEditor; @@ -1381,7 +1381,7 @@ CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1947; + CURRENT_PROJECT_VERSION = 1948; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = ""; GCC_C_LANGUAGE_STANDARD = gnu99; @@ -1416,7 +1416,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.11.5; - MARKETING_VERSION = 1.4.6; + MARKETING_VERSION = 1.4.7; ONLY_ACTIVE_ARCH = YES; PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.inputmethod.vChewing; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1449,7 +1449,7 @@ CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1947; + CURRENT_PROJECT_VERSION = 1948; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = ""; @@ -1479,7 +1479,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.11.5; - MARKETING_VERSION = 1.4.6; + MARKETING_VERSION = 1.4.7; PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.inputmethod.vChewing; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1563,7 +1563,7 @@ CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1947; + CURRENT_PROJECT_VERSION = 1948; DEVELOPMENT_TEAM = ""; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; @@ -1588,7 +1588,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.11.5; - MARKETING_VERSION = 1.4.6; + MARKETING_VERSION = 1.4.7; ONLY_ACTIVE_ARCH = YES; PRODUCT_BUNDLE_IDENTIFIER = "org.atelierInmu.vChewing.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1616,7 +1616,7 @@ CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1947; + CURRENT_PROJECT_VERSION = 1948; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; GCC_C_LANGUAGE_STANDARD = gnu99; @@ -1636,7 +1636,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.11.5; - MARKETING_VERSION = 1.4.6; + MARKETING_VERSION = 1.4.7; PRODUCT_BUNDLE_IDENTIFIER = "org.atelierInmu.vChewing.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "";