Pre Merge pull request !19 from ShikiSuen/upd/1.4.7

This commit is contained in:
ShikiSuen 2022-04-04 07:48:23 +00:00 committed by Gitee
commit eecb45712b
102 changed files with 12437 additions and 9623 deletions

171
.clang-format Normal file
View File

@ -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: '^<ext/.*\.h>'
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
...

56
.clang-format-swift.json Normal file
View File

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

39
.gitconfig_backup Normal file
View File

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

217
.gitignore vendored
View File

@ -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 *.dmg
*.dotCover
*.DotSettings.user
*.e2e
*.GhostDoc.xml
*.gpState
*.ilk
*.iobj
*.ipdb
*.jfm
*.jmconfig
*.ldf
*.lnk
*.log
*.mdf
*.meta
*.mm.*
*.mode1v3 *.mode1v3
*.msi
*.msix
*.msm
*.msp
*.ncb
*.ndf
*.nuget.props
*.nuget.targets
*.nupkg
*.nvuser
*.obj
*.odx.cs
*.opendb
*.opensdf
*.opt
*.pbxuser *.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 *.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 .build
.builds
.com.apple.timemachine.donotpresent
.cr/personal
.DocumentRevisions-V100
.DS_Store .DS_Store
.fake/
.fseventsd
.idea .idea
.idea/
.JustCode
.localhistory/
.LSOverride
.mfractor/
.ntvs_analysis.dat
.paket/paket.exe
.sass-cache/
.Spotlight-V100
.swiftpm .swiftpm
.TemporaryItems
.Trashes
.VolumeIcon.icns
.vs/
.vscode .vscode
._*
aclocal.m4
AppPackages/
artifacts/
ASALocalRun/
autom4te.cache/
AutoTest.Net/
Backup*/
BenchmarkDotNet.Artifacts/
bld/
build build
BundleArtifacts/
ClientBin/
config.make
config.status
Credits.rtf 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 project.xcworkspace
publish/
PublishScripts/
rcf/
ServiceFabricBackup/
Source/Data/* Source/Data/*
xcuserdata 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__/
~$*

11
AUTHORS Normal file
View File

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

View File

@ -1,383 +1,421 @@
#!/usr/bin/env swift
// Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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 Foundation
// MARK: - // MARK: -
fileprivate extension String { extension String {
mutating func regReplace(pattern: String, replaceWith: String = "") { fileprivate mutating func regReplace(pattern: String, replaceWith: String = "") {
// Ref: https://stackoverflow.com/a/40993403/4162914 && https://stackoverflow.com/a/71291137/4162914 // Ref: https://stackoverflow.com/a/40993403/4162914 && https://stackoverflow.com/a/71291137/4162914
do { do {
let regex = try NSRegularExpression(pattern: pattern, options: [.caseInsensitive, .anchorsMatchLines]) let regex = try NSRegularExpression(
let range = NSRange(self.startIndex..., in: self) pattern: pattern, options: [.caseInsensitive, .anchorsMatchLines])
self = regex.stringByReplacingMatches(in: self, options: [], range: range, withTemplate: replaceWith) let range = NSRange(self.startIndex..., in: self)
} catch { return } self = regex.stringByReplacingMatches(
} in: self, options: [], range: range, withTemplate: replaceWith)
} catch { return }
}
} }
fileprivate func getDocumentsDirectory() -> URL { private func getDocumentsDirectory() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
return paths[0] return paths[0]
} }
// MARK: - // MARK: -
// Ref: https://stackoverflow.com/a/32581409/4162914 // Ref: https://stackoverflow.com/a/32581409/4162914
fileprivate extension Float { extension Float {
func rounded(toPlaces places:Int) -> Float { fileprivate func rounded(toPlaces places: Int) -> Float {
let divisor = pow(10.0, Float(places)) let divisor = pow(10.0, Float(places))
return (self * divisor).rounded() / divisor return (self * divisor).rounded() / divisor
} }
} }
// MARK: - // MARK: -
// Ref: https://stackoverflow.com/a/41581695/4162914 // Ref: https://stackoverflow.com/a/41581695/4162914
precedencegroup ExponentiationPrecedence { precedencegroup ExponentiationPrecedence {
associativity: right associativity: right
higherThan: MultiplicationPrecedence higherThan: MultiplicationPrecedence
} }
infix operator ** : ExponentiationPrecedence infix operator **: ExponentiationPrecedence
func ** (_ base: Double, _ exp: Double) -> Double { func ** (_ base: Double, _ exp: Double) -> Double {
return pow(base, exp) return pow(base, exp)
} }
func ** (_ base: Float, _ exp: Float) -> Float { func ** (_ base: Float, _ exp: Float) -> Float {
return pow(base, exp) return pow(base, exp)
} }
// MARK: - // MARK: -
struct Entry { struct Entry {
var valPhone: String = "" var valPhone: String = ""
var valPhrase: String = "" var valPhrase: String = ""
var valWeight: Float = -1.0 var valWeight: Float = -1.0
var valCount: Int = 0 var valCount: Int = 0
} }
// MARK: - // 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" private let urlCHSforCustom: String = "./components/chs/phrases-custom-chs.txt"
fileprivate let url_CHS_MCBP: String = "./components/chs/phrases-mcbp-chs.txt" private let urlCHSforMCBP: String = "./components/chs/phrases-mcbp-chs.txt"
fileprivate let url_CHS_MOE: String = "./components/chs/phrases-moe-chs.txt" private let urlCHSforMOE: String = "./components/chs/phrases-moe-chs.txt"
fileprivate let url_CHS_VCHEW: String = "./components/chs/phrases-vchewing-chs.txt" private let urlCHSforVCHEW: String = "./components/chs/phrases-vchewing-chs.txt"
fileprivate let url_CHT_Custom: String = "./components/cht/phrases-custom-cht.txt" private let urlCHTforCustom: String = "./components/cht/phrases-custom-cht.txt"
fileprivate let url_CHT_MCBP: String = "./components/cht/phrases-mcbp-cht.txt" private let urlCHTforMCBP: String = "./components/cht/phrases-mcbp-cht.txt"
fileprivate let url_CHT_MOE: String = "./components/cht/phrases-moe-cht.txt" private let urlCHTforMOE: String = "./components/cht/phrases-moe-cht.txt"
fileprivate let url_CHT_VCHEW: String = "./components/cht/phrases-vchewing-cht.txt" private let urlCHTforVCHEW: String = "./components/cht/phrases-vchewing-cht.txt"
fileprivate let urlKanjiCore: String = "./components/common/char-kanji-core.txt" private let urlKanjiCore: String = "./components/common/char-kanji-core.txt"
fileprivate let urlPunctuation: String = "./components/common/data-punctuations.txt" private let urlPunctuation: String = "./components/common/data-punctuations.txt"
fileprivate let urlMiscBPMF: String = "./components/common/char-misc-bpmf.txt" private let urlMiscBPMF: String = "./components/common/char-misc-bpmf.txt"
fileprivate let urlMiscNonKanji: String = "./components/common/char-misc-nonkanji.txt" private let urlMiscNonKanji: String = "./components/common/char-misc-nonkanji.txt"
fileprivate let urlOutputCHS: String = "./data-chs.txt" private let urlOutputCHS: String = "./data-chs.txt"
fileprivate let urlOutputCHT: String = "./data-cht.txt" private let urlOutputCHT: String = "./data-cht.txt"
// MARK: - // MARK: -
func rawDictForPhrases(isCHS: Bool) -> [Entry] { func rawDictForPhrases(isCHS: Bool) -> [Entry] {
var arrEntryRAW: [Entry] = [] var arrEntryRAW: [Entry] = []
var strRAW: String = "" var strRAW: String = ""
let urlCustom: String = isCHS ? url_CHS_Custom : url_CHT_Custom let urlCustom: String = isCHS ? urlCHSforCustom : urlCHTforCustom
let urlMCBP: String = isCHS ? url_CHS_MCBP : url_CHT_MCBP let urlMCBP: String = isCHS ? urlCHSforMCBP : urlCHTforMCBP
let urlMOE: String = isCHS ? url_CHS_MOE : url_CHT_MOE let urlMOE: String = isCHS ? urlCHSforMOE : urlCHTforMOE
let urlVCHEW: String = isCHS ? url_CHS_VCHEW : url_CHT_VCHEW let urlVCHEW: String = isCHS ? urlCHSforVCHEW : urlCHTforVCHEW
let i18n: String = isCHS ? "簡體中文" : "繁體中文" let i18n: String = isCHS ? "簡體中文" : "繁體中文"
// //
do { do {
strRAW += try String(contentsOfFile: urlCustom, encoding: .utf8) strRAW += try String(contentsOfFile: urlCustom, encoding: .utf8)
strRAW += "\n" strRAW += "\n"
strRAW += try String(contentsOfFile: urlMCBP, encoding: .utf8) strRAW += try String(contentsOfFile: urlMCBP, encoding: .utf8)
strRAW += "\n" strRAW += "\n"
strRAW += try String(contentsOfFile: urlMOE, encoding: .utf8) strRAW += try String(contentsOfFile: urlMOE, encoding: .utf8)
strRAW += "\n" strRAW += "\n"
strRAW += try String(contentsOfFile: urlVCHEW, encoding: .utf8) strRAW += try String(contentsOfFile: urlVCHEW, encoding: .utf8)
} } catch {
catch { NSLog(" - Exception happened when reading raw phrases data.")
NSLog(" - Exception happened when reading raw phrases data.") return []
return [] }
} //
// strRAW = strRAW.replacingOccurrences(of: " #MACOS", with: "") // macOS
strRAW = strRAW.replacingOccurrences(of: " #MACOS", with: "") // macOS // CJKWhiteSpace (\x{3000}) to ASCII Space
// CJKWhiteSpace (\x{3000}) to ASCII Space // NonBreakWhiteSpace (\x{A0}) to ASCII Space
// NonBreakWhiteSpace (\x{A0}) to ASCII Space // Tab to ASCII Space
// Tab to ASCII Space // ASCII
// ASCII strRAW.regReplace(pattern: #"( +| +| +|\t+)+"#, replaceWith: " ")
strRAW.regReplace(pattern: #"( +| +| +|\t+)+"#, replaceWith: " ") strRAW.regReplace(pattern: #"(^ | $)"#, replaceWith: "") //
strRAW.regReplace(pattern: #"(^ | $)"#, replaceWith: "") // strRAW.regReplace(pattern: #"(\f+|\r+|\n+)+"#, replaceWith: "\n") // CR & Form Feed to LF,
strRAW.regReplace(pattern: #"(\f+|\r+|\n+)+"#, replaceWith: "\n") // CR & Form Feed to LF, strRAW.regReplace(pattern: #"^(#.*|.*#WIN32.*)$"#, replaceWith: "") // #+ WIN32
strRAW.regReplace(pattern: #"^(#.*|.*#WIN32.*)$"#, replaceWith: "") // #+ WIN32 //
// let arrData = Array(
let arrData = Array(NSOrderedSet(array: strRAW.components(separatedBy: "\n")).array as! [String]) NSOrderedSet(array: strRAW.components(separatedBy: "\n")).array as! [String])
for lineData in arrData { for lineData in arrData {
// //
let arrLineData = lineData.components(separatedBy: " ") let arrLineData = lineData.components(separatedBy: " ")
var varLineDataProcessed: String = "" var varLineDataProcessed: String = ""
var count = 0 var count = 0
for currentCell in arrLineData { for currentCell in arrLineData {
count += 1 count += 1
if count < 3 { if count < 3 {
varLineDataProcessed += currentCell + "\t" varLineDataProcessed += currentCell + "\t"
} else if count < arrLineData.count { } else if count < arrLineData.count {
varLineDataProcessed += currentCell + "-" varLineDataProcessed += currentCell + "-"
} else { } else {
varLineDataProcessed += currentCell varLineDataProcessed += currentCell
} }
} }
// Entry // Entry
let arrCells : [String] = varLineDataProcessed.components(separatedBy: "\t") let arrCells: [String] = varLineDataProcessed.components(separatedBy: "\t")
count = 0 // count = 0 //
var phone = "" var phone = ""
var phrase = "" var phrase = ""
var occurrence = 0 var occurrence = 0
for cell in arrCells { for cell in arrCells {
count += 1 count += 1
switch count { switch count {
case 1: phrase = cell case 1: phrase = cell
case 3: phone = cell case 3: phone = cell
case 2: occurrence = Int(cell) ?? 0 case 2: occurrence = Int(cell) ?? 0
default: break default: break
} }
} }
if phrase != "" { // if phrase != "" { //
arrEntryRAW += [Entry.init(valPhone: phone, valPhrase: phrase, valWeight: 0.0, valCount: occurrence)] arrEntryRAW += [
} Entry.init(
} valPhone: phone, valPhrase: phrase, valWeight: 0.0,
NSLog(" - \(i18n): 成功生成詞語語料辭典(權重待計算)。") valCount: occurrence)
return arrEntryRAW ]
}
}
NSLog(" - \(i18n): 成功生成詞語語料辭典(權重待計算)。")
return arrEntryRAW
} }
// MARK: - // MARK: -
func rawDictForKanjis(isCHS: Bool) -> [Entry] { func rawDictForKanjis(isCHS: Bool) -> [Entry] {
var arrEntryRAW: [Entry] = [] var arrEntryRAW: [Entry] = []
var strRAW: String = "" var strRAW: String = ""
let i18n: String = isCHS ? "簡體中文" : "繁體中文" let i18n: String = isCHS ? "簡體中文" : "繁體中文"
// //
do { do {
strRAW += try String(contentsOfFile: urlKanjiCore, encoding: .utf8) strRAW += try String(contentsOfFile: urlKanjiCore, encoding: .utf8)
} } catch {
catch { NSLog(" - Exception happened when reading raw core kanji data.")
NSLog(" - Exception happened when reading raw core kanji data.") return []
return [] }
} //
// strRAW = strRAW.replacingOccurrences(of: " #MACOS", with: "") // macOS
strRAW = strRAW.replacingOccurrences(of: " #MACOS", with: "") // macOS // CJKWhiteSpace (\x{3000}) to ASCII Space
// CJKWhiteSpace (\x{3000}) to ASCII Space // NonBreakWhiteSpace (\x{A0}) to ASCII Space
// NonBreakWhiteSpace (\x{A0}) to ASCII Space // Tab to ASCII Space
// Tab to ASCII Space // ASCII
// ASCII strRAW.regReplace(pattern: #"( +| +| +|\t+)+"#, replaceWith: " ")
strRAW.regReplace(pattern: #"( +| +| +|\t+)+"#, replaceWith: " ") strRAW.regReplace(pattern: #"(^ | $)"#, replaceWith: "") //
strRAW.regReplace(pattern: #"(^ | $)"#, replaceWith: "") // strRAW.regReplace(pattern: #"(\f+|\r+|\n+)+"#, replaceWith: "\n") // CR & Form Feed to LF,
strRAW.regReplace(pattern: #"(\f+|\r+|\n+)+"#, replaceWith: "\n") // CR & Form Feed to LF, strRAW.regReplace(pattern: #"^(#.*|.*#WIN32.*)$"#, replaceWith: "") // #+ WIN32
strRAW.regReplace(pattern: #"^(#.*|.*#WIN32.*)$"#, replaceWith: "") // #+ WIN32 //
// let arrData = Array(
let arrData = Array(NSOrderedSet(array: strRAW.components(separatedBy: "\n")).array as! [String]) NSOrderedSet(array: strRAW.components(separatedBy: "\n")).array as! [String])
var varLineData: String = "" var varLineData: String = ""
for lineData in arrData { for lineData in arrData {
// 1,2,4 1,3,4 // 1,2,4 1,3,4
let varLineDataPre = lineData.components(separatedBy: " ").prefix(isCHS ? 2 : 1).joined(separator: "\t") let varLineDataPre = lineData.components(separatedBy: " ").prefix(isCHS ? 2 : 1)
let varLineDataPost = lineData.components(separatedBy: " ").suffix(isCHS ? 1 : 2).joined(separator: "\t") .joined(
varLineData = varLineDataPre + "\t" + varLineDataPost separator: "\t")
let arrLineData = varLineData.components(separatedBy: " ") let varLineDataPost = lineData.components(separatedBy: " ").suffix(isCHS ? 1 : 2)
var varLineDataProcessed: String = "" .joined(
var count = 0 separator: "\t")
for currentCell in arrLineData { varLineData = varLineDataPre + "\t" + varLineDataPost
count += 1 let arrLineData = varLineData.components(separatedBy: " ")
if count < 3 { var varLineDataProcessed: String = ""
varLineDataProcessed += currentCell + "\t" var count = 0
} else if count < arrLineData.count { for currentCell in arrLineData {
varLineDataProcessed += currentCell + "-" count += 1
} else { if count < 3 {
varLineDataProcessed += currentCell varLineDataProcessed += currentCell + "\t"
} } else if count < arrLineData.count {
} varLineDataProcessed += currentCell + "-"
// Entry } else {
let arrCells : [String] = varLineDataProcessed.components(separatedBy: "\t") varLineDataProcessed += currentCell
count = 0 // }
var phone = "" }
var phrase = "" // Entry
var occurrence = 0 let arrCells: [String] = varLineDataProcessed.components(separatedBy: "\t")
for cell in arrCells { count = 0 //
count += 1 var phone = ""
switch count { var phrase = ""
case 1: phrase = cell var occurrence = 0
case 3: phone = cell for cell in arrCells {
case 2: occurrence = Int(cell) ?? 0 count += 1
default: break switch count {
} case 1: phrase = cell
} case 3: phone = cell
if phrase != "" { // case 2: occurrence = Int(cell) ?? 0
arrEntryRAW += [Entry.init(valPhone: phone, valPhrase: phrase, valWeight: 0.0, valCount: occurrence)] default: break
} }
} }
NSLog(" - \(i18n): 成功生成單字語料辭典(權重待計算)。") if phrase != "" { //
return arrEntryRAW arrEntryRAW += [
Entry.init(
valPhone: phone, valPhrase: phrase, valWeight: 0.0,
valCount: occurrence)
]
}
}
NSLog(" - \(i18n): 成功生成單字語料辭典(權重待計算)。")
return arrEntryRAW
} }
// MARK: - // MARK: -
func rawDictForNonKanjis(isCHS: Bool) -> [Entry] { func rawDictForNonKanjis(isCHS: Bool) -> [Entry] {
var arrEntryRAW: [Entry] = [] var arrEntryRAW: [Entry] = []
var strRAW: String = "" var strRAW: String = ""
let i18n: String = isCHS ? "簡體中文" : "繁體中文" let i18n: String = isCHS ? "簡體中文" : "繁體中文"
// //
do { do {
strRAW += try String(contentsOfFile: urlMiscBPMF, encoding: .utf8) strRAW += try String(contentsOfFile: urlMiscBPMF, encoding: .utf8)
strRAW += "\n" strRAW += "\n"
strRAW += try String(contentsOfFile: urlMiscNonKanji, encoding: .utf8) strRAW += try String(contentsOfFile: urlMiscNonKanji, encoding: .utf8)
} } catch {
catch { NSLog(" - Exception happened when reading raw core kanji data.")
NSLog(" - Exception happened when reading raw core kanji data.") return []
return [] }
} //
// strRAW = strRAW.replacingOccurrences(of: " #MACOS", with: "") // macOS
strRAW = strRAW.replacingOccurrences(of: " #MACOS", with: "") // macOS // CJKWhiteSpace (\x{3000}) to ASCII Space
// CJKWhiteSpace (\x{3000}) to ASCII Space // NonBreakWhiteSpace (\x{A0}) to ASCII Space
// NonBreakWhiteSpace (\x{A0}) to ASCII Space // Tab to ASCII Space
// Tab to ASCII Space // ASCII
// ASCII strRAW.regReplace(pattern: #"( +| +| +|\t+)+"#, replaceWith: " ")
strRAW.regReplace(pattern: #"( +| +| +|\t+)+"#, replaceWith: " ") strRAW.regReplace(pattern: #"(^ | $)"#, replaceWith: "") //
strRAW.regReplace(pattern: #"(^ | $)"#, replaceWith: "") // strRAW.regReplace(pattern: #"(\f+|\r+|\n+)+"#, replaceWith: "\n") // CR & Form Feed to LF,
strRAW.regReplace(pattern: #"(\f+|\r+|\n+)+"#, replaceWith: "\n") // CR & Form Feed to LF, strRAW.regReplace(pattern: #"^(#.*|.*#WIN32.*)$"#, replaceWith: "") // #+ WIN32
strRAW.regReplace(pattern: #"^(#.*|.*#WIN32.*)$"#, replaceWith: "") // #+ WIN32 //
// let arrData = Array(
let arrData = Array(NSOrderedSet(array: strRAW.components(separatedBy: "\n")).array as! [String]) NSOrderedSet(array: strRAW.components(separatedBy: "\n")).array as! [String])
var varLineData: String = "" var varLineData: String = ""
for lineData in arrData { for lineData in arrData {
varLineData = lineData varLineData = lineData
// //
varLineData = varLineData.components(separatedBy: " ").prefix(3).joined(separator: "\t") // varLineData = varLineData.components(separatedBy: " ").prefix(3).joined(
let arrLineData = varLineData.components(separatedBy: " ") separator: "\t") //
var varLineDataProcessed: String = "" let arrLineData = varLineData.components(separatedBy: " ")
var count = 0 var varLineDataProcessed: String = ""
for currentCell in arrLineData { var count = 0
count += 1 for currentCell in arrLineData {
if count < 3 { count += 1
varLineDataProcessed += currentCell + "\t" if count < 3 {
} else if count < arrLineData.count { varLineDataProcessed += currentCell + "\t"
varLineDataProcessed += currentCell + "-" } else if count < arrLineData.count {
} else { varLineDataProcessed += currentCell + "-"
varLineDataProcessed += currentCell } else {
} varLineDataProcessed += currentCell
} }
// Entry }
let arrCells : [String] = varLineDataProcessed.components(separatedBy: "\t") // Entry
count = 0 // let arrCells: [String] = varLineDataProcessed.components(separatedBy: "\t")
var phone = "" count = 0 //
var phrase = "" var phone = ""
var occurrence = 0 var phrase = ""
for cell in arrCells { var occurrence = 0
count += 1 for cell in arrCells {
switch count { count += 1
case 1: phrase = cell switch count {
case 3: phone = cell case 1: phrase = cell
case 2: occurrence = Int(cell) ?? 0 case 3: phone = cell
default: break case 2: occurrence = Int(cell) ?? 0
} default: break
} }
if phrase != "" { // }
arrEntryRAW += [Entry.init(valPhone: phone, valPhrase: phrase, valWeight: 0.0, valCount: occurrence)] if phrase != "" { //
} arrEntryRAW += [
} Entry.init(
NSLog(" - \(i18n): 成功生成非漢字語料辭典(權重待計算)。") valPhone: phone, valPhrase: phrase, valWeight: 0.0,
return arrEntryRAW valCount: occurrence)
]
}
}
NSLog(" - \(i18n): 成功生成非漢字語料辭典(權重待計算)。")
return arrEntryRAW
} }
func weightAndSort(_ arrStructUncalculated: [Entry], isCHS: Bool) -> [Entry] { func weightAndSort(_ arrStructUncalculated: [Entry], isCHS: Bool) -> [Entry] {
let i18n: String = isCHS ? "簡體中文" : "繁體中文" let i18n: String = isCHS ? "簡體中文" : "繁體中文"
var arrStructCalculated: [Entry] = [] var arrStructCalculated: [Entry] = []
let fscale: Float = 2.7 let fscale: Float = 2.7
var norm: Float = 0.0 var norm: Float = 0.0
for entry in arrStructUncalculated { for entry in arrStructUncalculated {
if entry.valCount >= 0 { if entry.valCount >= 0 {
norm += fscale**(Float(entry.valPhrase.count) / 3.0 - 1.0) * Float(entry.valCount) norm += fscale ** (Float(entry.valPhrase.count) / 3.0 - 1.0)
} * Float(entry.valCount)
} }
// norm norm }
// // norm norm
// 1 0 0.5 //
for entry in arrStructUncalculated { // 1 0 0.5
var weight: Float = 0 for entry in arrStructUncalculated {
switch entry.valCount { var weight: Float = 0
case -2: // switch entry.valCount {
weight = -13 case -2: //
case -1: // weight = -13
weight = -13 case -1: //
case 0: // weight = -13
weight = log10(fscale**(Float(entry.valPhrase.count) / 3.0 - 1.0) * 0.5 / norm) case 0: //
default: weight = log10(
weight = log10(fscale**(Float(entry.valPhrase.count) / 3.0 - 1.0) * Float(entry.valCount) / norm) // Credit: MJHsieh. fscale ** (Float(entry.valPhrase.count) / 3.0 - 1.0) * 0.5 / norm)
} default:
let weightRounded: Float = weight.rounded(toPlaces: 3) // weight = log10(
arrStructCalculated += [Entry.init(valPhone: entry.valPhone, valPhrase: entry.valPhrase, valWeight: weightRounded, valCount: entry.valCount)] fscale ** (Float(entry.valPhrase.count) / 3.0 - 1.0)
} * Float(entry.valCount) / norm) // Credit: MJHsieh.
NSLog(" - \(i18n): 成功計算權重。") }
// ========================================== let weightRounded: Float = weight.rounded(toPlaces: 3) //
// arrStructCalculated += [
let arrStructSorted: [Entry] = arrStructCalculated.sorted(by: {(lhs, rhs) -> Bool in return (lhs.valPhone, rhs.valCount) < (rhs.valPhone, lhs.valCount)}) Entry.init(
NSLog(" - \(i18n): 排序整理完畢,準備編譯要寫入的檔案內容。") valPhone: entry.valPhone, valPhrase: entry.valPhrase, valWeight: weightRounded,
return arrStructSorted 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) { func fileOutput(isCHS: Bool) {
let i18n: String = isCHS ? "簡體中文" : "繁體中文" let i18n: String = isCHS ? "簡體中文" : "繁體中文"
let pathOutput = urlCurrentFolder.appendingPathComponent(isCHS ? urlOutputCHS : urlOutputCHT) let pathOutput = urlCurrentFolder.appendingPathComponent(
var strPrintLine = "" isCHS ? urlOutputCHS : urlOutputCHT)
// var strPrintLine = ""
do { //
strPrintLine += try String(contentsOfFile: urlPunctuation, encoding: .utf8) do {
} strPrintLine += try String(contentsOfFile: urlPunctuation, encoding: .utf8)
catch { } catch {
NSLog(" - \(i18n): Exception happened when reading raw punctuation data.") NSLog(" - \(i18n): Exception happened when reading raw punctuation data.")
} }
NSLog(" - \(i18n): 成功插入標點符號與西文字母數據。") NSLog(" - \(i18n): 成功插入標點符號與西文字母數據。")
// //
var arrStructUnified: [Entry] = [] var arrStructUnified: [Entry] = []
arrStructUnified += rawDictForKanjis(isCHS: isCHS) arrStructUnified += rawDictForKanjis(isCHS: isCHS)
arrStructUnified += rawDictForNonKanjis(isCHS: isCHS) arrStructUnified += rawDictForNonKanjis(isCHS: isCHS)
arrStructUnified += rawDictForPhrases(isCHS: isCHS) arrStructUnified += rawDictForPhrases(isCHS: isCHS)
// //
arrStructUnified = weightAndSort(arrStructUnified, isCHS: isCHS) arrStructUnified = weightAndSort(arrStructUnified, isCHS: isCHS)
for entry in arrStructUnified { for entry in arrStructUnified {
strPrintLine += entry.valPhone + " " + entry.valPhrase + " " + String(entry.valWeight) + "\n" strPrintLine +=
} entry.valPhone + " " + entry.valPhrase + " " + String(entry.valWeight)
NSLog(" - \(i18n): 要寫入檔案的內容編譯完畢。") + "\n"
do { }
try strPrintLine.write(to: pathOutput, atomically: false, encoding: .utf8) NSLog(" - \(i18n): 要寫入檔案的內容編譯完畢。")
} do {
catch { try strPrintLine.write(to: pathOutput, atomically: false, encoding: .utf8)
NSLog(" - \(i18n): Error on writing strings to file: \(error)") } catch {
} NSLog(" - \(i18n): Error on writing strings to file: \(error)")
NSLog(" - \(i18n): 寫入完成。") }
NSLog(" - \(i18n): 寫入完成。")
} }
// MARK: - // MARK: -
func main() { func main() {
NSLog("// 準備編譯繁體中文核心語料檔案。") NSLog("// 準備編譯繁體中文核心語料檔案。")
fileOutput(isCHS: false) fileOutput(isCHS: false)
NSLog("// 準備編譯簡體中文核心語料檔案。") NSLog("// 準備編譯簡體中文核心語料檔案。")
fileOutput(isCHS: true) fileOutput(isCHS: true)
} }
main() main()

View File

@ -1,20 +1,27 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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 Cocoa
@ -23,9 +30,11 @@ private let kTargetBin = "vChewing"
private let kTargetType = "app" private let kTargetType = "app"
private let kTargetBundle = "vChewing.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 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 kDestinationPartial = urlDestinationPartial.path
private let kTargetPartialPath = urlTargetPartial.path private let kTargetPartialPath = urlTargetPartial.path
@ -35,251 +44,300 @@ private let kTranslocationRemovalTickInterval: TimeInterval = 0.5
private let kTranslocationRemovalDeadline: TimeInterval = 60.0 private let kTranslocationRemovalDeadline: TimeInterval = 60.0
@NSApplicationMain @NSApplicationMain
@objc (AppDelegate) @objc(AppDelegate)
class AppDelegate: NSWindowController, NSApplicationDelegate { class AppDelegate: NSWindowController, NSApplicationDelegate {
@IBOutlet weak private var installButton: NSButton! @IBOutlet weak private var installButton: NSButton!
@IBOutlet weak private var cancelButton: NSButton! @IBOutlet weak private var cancelButton: NSButton!
@IBOutlet weak private var progressSheet: NSWindow! @IBOutlet weak private var progressSheet: NSWindow!
@IBOutlet weak private var progressIndicator: NSProgressIndicator! @IBOutlet weak private var progressIndicator: NSProgressIndicator!
@IBOutlet weak private var appVersionLabel: NSTextField! @IBOutlet weak private var appVersionLabel: NSTextField!
@IBOutlet weak private var appCopyrightLabel: NSTextField! @IBOutlet weak private var appCopyrightLabel: NSTextField!
@IBOutlet private var appEULAContent: NSTextView! @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
func runAlertPanel(title: String, message: String, buttonTitle: String) { private var archiveUtil: ArchiveUtil?
let alert = NSAlert() private var installingVersion = ""
alert.alertStyle = .informational private var upgrading = false
alert.messageText = title private var translocationRemovalStartTime: Date?
alert.informativeText = message private var currentVersionNumber: Int = 0
alert.addButton(withTitle: buttonTitle)
alert.runModal()
}
func applicationDidFinishLaunching(_ notification: Notification) { func runAlertPanel(title: String, message: String, buttonTitle: String) {
guard let installingVersion = Bundle.main.infoDictionary?[kCFBundleVersionKey as String] as? String, let alert = NSAlert()
let versionString = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String else { alert.alertStyle = .informational
return alert.messageText = title
} alert.informativeText = message
self.installingVersion = installingVersion alert.addButton(withTitle: buttonTitle)
self.archiveUtil = ArchiveUtil(appName: kTargetBin, targetAppBundleName: kTargetBundle) alert.runModal()
_ = archiveUtil?.validateIfNotarizedArchiveExists() }
cancelButton.nextKeyView = installButton func applicationDidFinishLaunching(_ notification: Notification) {
installButton.nextKeyView = cancelButton guard
if let cell = installButton.cell as? NSButtonCell { let installingVersion = Bundle.main.infoDictionary?[kCFBundleVersionKey as String]
window?.defaultButtonCell = cell as? String,
} let versionString = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
else {
if let copyrightLabel = Bundle.main.localizedInfoDictionary?["NSHumanReadableCopyright"] as? String { return
appCopyrightLabel.stringValue = copyrightLabel }
} self.installingVersion = installingVersion
if let eulaContent = Bundle.main.localizedInfoDictionary?["CFEULAContent"] as? String { self.archiveUtil = ArchiveUtil(appName: kTargetBin, targetAppBundleName: kTargetBundle)
appEULAContent.string = eulaContent _ = archiveUtil?.validateIfNotarizedArchiveExists()
}
appVersionLabel.stringValue = String(format: "%@ Build %@", versionString, installingVersion)
window?.title = String(format: NSLocalizedString("%@ (for version %@, r%@)", comment: ""), window?.title ?? "", versionString, installingVersion) cancelButton.nextKeyView = installButton
window?.standardWindowButton(.closeButton)?.isHidden = true installButton.nextKeyView = cancelButton
window?.standardWindowButton(.miniaturizeButton)?.isHidden = true if let cell = installButton.cell as? NSButtonCell {
window?.standardWindowButton(.zoomButton)?.isHidden = true window?.defaultButtonCell = cell
}
if FileManager.default.fileExists(atPath: (kTargetPartialPath as NSString).expandingTildeInPath) { if let copyrightLabel = Bundle.main.localizedInfoDictionary?["NSHumanReadableCopyright"]
let currentBundle = Bundle(path: (kTargetPartialPath as NSString).expandingTildeInPath) as? String
let shortVersion = currentBundle?.infoDictionary?["CFBundleShortVersionString"] as? String {
let currentVersion = currentBundle?.infoDictionary?[kCFBundleVersionKey as String] as? String appCopyrightLabel.stringValue = copyrightLabel
currentVersionNumber = (currentVersion as NSString?)?.integerValue ?? 0 }
if shortVersion != nil, let currentVersion = currentVersion, currentVersion.compare(installingVersion, options: .numeric) == .orderedAscending { if let eulaContent = Bundle.main.localizedInfoDictionary?["CFEULAContent"] as? String {
upgrading = true appEULAContent.string = eulaContent
} }
} appVersionLabel.stringValue = String(
format: "%@ Build %@", versionString, installingVersion)
if upgrading { window?.title = String(
installButton.title = NSLocalizedString("Upgrade", comment: "") 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() if FileManager.default.fileExists(
window?.orderFront(self) atPath: (kTargetPartialPath as NSString).expandingTildeInPath)
NSApp.activate(ignoringOtherApps: true) {
} 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) { if upgrading {
cancelButton.isEnabled = false installButton.title = NSLocalizedString("Upgrade", comment: "")
installButton.isEnabled = false }
removeThenInstallInputMethod()
}
@objc func timerTick(_ timer: Timer) { window?.center()
let elapsed = Date().timeIntervalSince(translocationRemovalStartTime ?? Date()) window?.orderFront(self)
if elapsed >= kTranslocationRemovalDeadline { NSApp.activate(ignoringOtherApps: true)
timer.invalidate() }
window?.endSheet(progressSheet, returnCode: .cancel)
} else if appBundleChronoshiftedToARandomizedPath(kTargetPartialPath) == false {
progressIndicator.doubleValue = 1.0
timer.invalidate()
window?.endSheet(progressSheet, returnCode: .continue)
}
}
func removeThenInstallInputMethod() { @IBAction func agreeAndInstallAction(_ sender: AnyObject) {
if FileManager.default.fileExists(atPath: (kTargetPartialPath as NSString).expandingTildeInPath) == false { cancelButton.isEnabled = false
self.installInputMethod(previousExists: false, previousVersionNotFullyDeactivatedWarning: false) installButton.isEnabled = false
return 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)
}
}
// func removeThenInstallInputMethod() {
do { if FileManager.default.fileExists(
let sourceDir = (kDestinationPartial as NSString).expandingTildeInPath atPath: (kTargetPartialPath as NSString).expandingTildeInPath)
let fileManager = FileManager.default == false
let fileURLString = String(format: "%@/%@", sourceDir, kTargetBundle) {
let fileURL = URL(fileURLWithPath: fileURLString) self.installInputMethod(
previousExists: false, previousVersionNotFullyDeactivatedWarning: false)
// return
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() let shouldWaitForTranslocationRemoval =
killTask.launchPath = "/usr/bin/killall" appBundleChronoshiftedToARandomizedPath(kTargetPartialPath)
killTask.arguments = ["-9", kTargetBin] && (window?.responds(to: #selector(NSWindow.beginSheet(_:completionHandler:))) ?? false)
killTask.launch()
killTask.waitUntilExit()
if shouldWaitForTranslocationRemoval { //
progressIndicator.startAnimation(self) do {
window?.beginSheet(progressSheet) { returnCode in let sourceDir = (kDestinationPartial as NSString).expandingTildeInPath
DispatchQueue.main.async { let fileManager = FileManager.default
if returnCode == .continue { let fileURLString = String(format: "%@/%@", sourceDir, kTargetBundle)
self.installInputMethod(previousExists: true, previousVersionNotFullyDeactivatedWarning: false) let fileURL = URL(fileURLWithPath: fileURLString)
} else {
self.installInputMethod(previousExists: true, previousVersionNotFullyDeactivatedWarning: true)
}
}
}
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 { } else {
self.installInputMethod(previousExists: false, previousVersionNotFullyDeactivatedWarning: false) self.installInputMethod(
} previousExists: false, previousVersionNotFullyDeactivatedWarning: false)
} }
}
func installInputMethod(previousExists: Bool, previousVersionNotFullyDeactivatedWarning warning: Bool) { func installInputMethod(
guard let targetBundle = archiveUtil?.unzipNotarizedArchive() ?? Bundle.main.path(forResource: kTargetBin, ofType: kTargetType) else { previousExists: Bool, previousVersionNotFullyDeactivatedWarning warning: Bool
return ) {
} guard
let cpTask = Process() let targetBundle = archiveUtil?.unzipNotarizedArchive()
cpTask.launchPath = "/bin/cp" ?? Bundle.main.path(forResource: kTargetBin, ofType: kTargetType)
cpTask.arguments = ["-R", targetBundle, (kDestinationPartial as NSString).expandingTildeInPath] else {
cpTask.launch() return
cpTask.waitUntilExit() }
let cpTask = Process()
cpTask.launchPath = "/bin/cp"
cpTask.arguments = [
"-R", targetBundle, (kDestinationPartial as NSString).expandingTildeInPath,
]
cpTask.launch()
cpTask.waitUntilExit()
if cpTask.terminationStatus != 0 { if cpTask.terminationStatus != 0 {
runAlertPanel(title: NSLocalizedString("Install Failed", comment: ""), runAlertPanel(
message: NSLocalizedString("Cannot copy the file to the destination.", comment: ""), title: NSLocalizedString("Install Failed", comment: ""),
buttonTitle: NSLocalizedString("Cancel", comment: "")) message: NSLocalizedString("Cannot copy the file to the destination.", comment: ""),
endAppWithDelay() buttonTitle: NSLocalizedString("Cancel", comment: ""))
} endAppWithDelay()
}
guard let imeBundle = Bundle(path: (kTargetPartialPath as NSString).expandingTildeInPath), guard let imeBundle = Bundle(path: (kTargetPartialPath as NSString).expandingTildeInPath),
let imeIdentifier = imeBundle.bundleIdentifier let imeIdentifier = imeBundle.bundleIdentifier
else { else {
endAppWithDelay() endAppWithDelay()
return return
} }
let imeBundleURL = imeBundle.bundleURL let imeBundleURL = imeBundle.bundleURL
var inputSource = InputSourceHelper.inputSource(for: imeIdentifier) var inputSource = InputSourceHelper.inputSource(for: imeIdentifier)
if inputSource == nil { if inputSource == nil {
NSLog("Registering input source \(imeIdentifier) at \(imeBundleURL.absoluteString)."); NSLog("Registering input source \(imeIdentifier) at \(imeBundleURL.absoluteString).")
let status = InputSourceHelper.registerTnputSource(at: imeBundleURL) let status = InputSourceHelper.registerTnputSource(at: imeBundleURL)
if !status { if !status {
let message = String(format: NSLocalizedString("Cannot find input source %@ after registration.", comment: ""), imeIdentifier) let message = String(
runAlertPanel(title: NSLocalizedString("Fatal Error", comment: ""), message: message, buttonTitle: NSLocalizedString("Abort", comment: "")) format: NSLocalizedString(
endAppWithDelay() "Cannot find input source %@ after registration.", comment: ""),
return imeIdentifier)
} runAlertPanel(
title: NSLocalizedString("Fatal Error", comment: ""), message: message,
buttonTitle: NSLocalizedString("Abort", comment: ""))
endAppWithDelay()
return
}
inputSource = InputSourceHelper.inputSource(for: imeIdentifier) inputSource = InputSourceHelper.inputSource(for: imeIdentifier)
if inputSource == nil { if inputSource == nil {
let message = String(format: NSLocalizedString("Cannot find input source %@ after registration.", comment: ""), imeIdentifier) let message = String(
runAlertPanel(title: NSLocalizedString("Fatal Error", comment: ""), message: message, buttonTitle: NSLocalizedString("Abort", comment: "")) 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 var isMacOS12OrAbove = false
if #available(macOS 12.0, *) { if #available(macOS 12.0, *) {
NSLog("macOS 12 or later detected."); NSLog("macOS 12 or later detected.")
isMacOS12OrAbove = true isMacOS12OrAbove = true
} else { } else {
NSLog("Installer runs with the pre-macOS 12 flow."); 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+, // 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* // 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 // enabled in the user's current set of IMEs (which means the IME does not show up in
// the user's input menu). // the user's input menu).
var mainInputSourceEnabled = InputSourceHelper.inputSourceEnabled(for: inputSource!) var mainInputSourceEnabled = InputSourceHelper.inputSourceEnabled(for: inputSource!)
if !mainInputSourceEnabled || isMacOS12OrAbove { if !mainInputSourceEnabled || isMacOS12OrAbove {
mainInputSourceEnabled = InputSourceHelper.enable(inputSource: inputSource!) mainInputSourceEnabled = InputSourceHelper.enable(inputSource: inputSource!)
if (mainInputSourceEnabled) { if mainInputSourceEnabled {
NSLog("Input method enabled: \(imeIdentifier)"); NSLog("Input method enabled: \(imeIdentifier)")
} else { } else {
NSLog("Failed to enable input method: \(imeIdentifier)"); 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()
}
}
func endAppWithDelay() { // Alert Panel
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) { let ntfPostInstall = NSAlert()
NSApp.terminate(self) 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) { func endAppWithDelay() {
NSApp.terminate(self) DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) {
} NSApp.terminate(self)
}
}
func windowWillClose(_ Notification: Notification) { @IBAction func cancelAction(_ sender: AnyObject) {
NSApp.terminate(self) NSApp.terminate(self)
} }
func windowWillClose(_ notification: Notification) {
NSApp.terminate(self)
}
} }

View File

@ -1,117 +1,134 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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 Cocoa
struct ArchiveUtil { struct ArchiveUtil {
var appName: String var appName: String
var targetAppBundleName: String var targetAppBundleName: String
init(appName: String, targetAppBundleName: String) { init(appName: String, targetAppBundleName: String) {
self.appName = appName self.appName = appName
self.targetAppBundleName = targetAppBundleName self.targetAppBundleName = targetAppBundleName
} }
// Returns YES if (1) a zip file under // Returns YES if (1) a zip file under
// Resources/NotarizedArchives/$_appName-$bundleVersion.zip exists, and (2) if // Resources/NotarizedArchives/$_appName-$bundleVersion.zip exists, and (2) if
// Resources/$_invalidAppBundleName does not exist. // Resources/$_invalidAppBundleName does not exist.
func validateIfNotarizedArchiveExists() -> Bool { func validateIfNotarizedArchiveExists() -> Bool {
guard let resourePath = Bundle.main.resourcePath, guard let resourePath = Bundle.main.resourcePath,
let notarizedArchivesPath = notarizedArchivesPath, let notarizedArchivesPath = notarizedArchivesPath,
let notarizedArchive = notarizedArchive, let notarizedArchive = notarizedArchive,
let notarizedArchivesContent: [String] = try? FileManager.default.subpathsOfDirectory(atPath: notarizedArchivesPath) let notarizedArchivesContent: [String] = try? FileManager.default.subpathsOfDirectory(
else { atPath: notarizedArchivesPath)
return false else {
} return false
}
let devModeAppBundlePath = (resourePath as NSString).appendingPathComponent(targetAppBundleName) let devModeAppBundlePath = (resourePath as NSString).appendingPathComponent(targetAppBundleName)
let count = notarizedArchivesContent.count let count = notarizedArchivesContent.count
let notarizedArchiveExists = FileManager.default.fileExists(atPath: notarizedArchive) let notarizedArchiveExists = FileManager.default.fileExists(atPath: notarizedArchive)
let devModeAppBundleExists = FileManager.default.fileExists(atPath: devModeAppBundlePath) let devModeAppBundleExists = FileManager.default.fileExists(atPath: devModeAppBundlePath)
if count > 0 { if count > 0 {
if count != 1 || !notarizedArchiveExists || devModeAppBundleExists { if count != 1 || !notarizedArchiveExists || devModeAppBundleExists {
let alert = NSAlert() let alert = NSAlert()
alert.alertStyle = .informational alert.alertStyle = .informational
alert.messageText = "Internal Error" alert.messageText = "Internal Error"
alert.informativeText = "devMode installer, expected archive name: \(notarizedArchive), " + alert.informativeText =
"archive exists: \(notarizedArchiveExists), devMode app bundle exists: \(devModeAppBundleExists)" "devMode installer, expected archive name: \(notarizedArchive), "
alert.addButton(withTitle: "Terminate") + "archive exists: \(notarizedArchiveExists), devMode app bundle exists: \(devModeAppBundleExists)"
alert.runModal() alert.addButton(withTitle: "Terminate")
NSApp.terminate(nil) alert.runModal()
} else { NSApp.terminate(nil)
return true } else {
} return true
} }
}
if !devModeAppBundleExists { if !devModeAppBundleExists {
let alert = NSAlert() let alert = NSAlert()
alert.alertStyle = .informational alert.alertStyle = .informational
alert.messageText = "Internal Error" alert.messageText = "Internal Error"
alert.informativeText = "Dev target bundle does not exist: \(devModeAppBundlePath)" alert.informativeText = "Dev target bundle does not exist: \(devModeAppBundlePath)"
alert.addButton(withTitle: "Terminate") alert.addButton(withTitle: "Terminate")
alert.runModal() alert.runModal()
NSApp.terminate(nil) NSApp.terminate(nil)
} }
return false return false
} }
func unzipNotarizedArchive() -> String? { func unzipNotarizedArchive() -> String? {
if !self.validateIfNotarizedArchiveExists() { if !self.validateIfNotarizedArchiveExists() {
return nil return nil
} }
guard let notarizedArchive = notarizedArchive, guard let notarizedArchive = notarizedArchive,
let resourcePath = Bundle.main.resourcePath else { let resourcePath = Bundle.main.resourcePath
return nil else {
} return nil
let tempFilePath = (NSTemporaryDirectory() as NSString).appendingPathComponent(UUID().uuidString) }
let arguments: [String] = [notarizedArchive, "-d", tempFilePath] let tempFilePath = (NSTemporaryDirectory() as NSString).appendingPathComponent(
let unzipTask = Process() UUID().uuidString)
unzipTask.launchPath = "/usr/bin/unzip" let arguments: [String] = [notarizedArchive, "-d", tempFilePath]
unzipTask.currentDirectoryPath = resourcePath let unzipTask = Process()
unzipTask.arguments = arguments unzipTask.launchPath = "/usr/bin/unzip"
unzipTask.launch() unzipTask.currentDirectoryPath = resourcePath
unzipTask.waitUntilExit() unzipTask.arguments = arguments
unzipTask.launch()
unzipTask.waitUntilExit()
assert(unzipTask.terminationStatus == 0, "Must successfully unzipped") assert(unzipTask.terminationStatus == 0, "Must successfully unzipped")
let result = (tempFilePath as NSString).appendingPathComponent(targetAppBundleName) let result = (tempFilePath as NSString).appendingPathComponent(targetAppBundleName)
assert(FileManager.default.fileExists(atPath: result), "App bundle must be unzipped at \(result).") assert(
return result FileManager.default.fileExists(atPath: result),
} "App bundle must be unzipped at \(result).")
return result
}
private var notarizedArchivesPath: String? { private var notarizedArchivesPath: String? {
guard let resourePath = Bundle.main.resourcePath else { guard let resourePath = Bundle.main.resourcePath else {
return nil return nil
} }
let notarizedArchivesPath = (resourePath as NSString).appendingPathComponent("NotarizedArchives") let notarizedArchivesPath = (resourePath as NSString).appendingPathComponent(
return notarizedArchivesPath "NotarizedArchives")
} return notarizedArchivesPath
}
private var notarizedArchive: String? { private var notarizedArchive: String? {
guard let notarizedArchivesPath = notarizedArchivesPath, guard let notarizedArchivesPath = notarizedArchivesPath,
let bundleVersion = Bundle.main.infoDictionary?[kCFBundleVersionKey as String] as? String else { let bundleVersion = Bundle.main.infoDictionary?[kCFBundleVersionKey as String]
return nil as? String
} else {
let notarizedArchiveBasename = "\(appName)-r\(bundleVersion).zip" return nil
let notarizedArchive = (notarizedArchivesPath as NSString).appendingPathComponent(notarizedArchiveBasename) }
return notarizedArchive let notarizedArchiveBasename = "\(appName)-r\(bundleVersion).zip"
} let notarizedArchive = (notarizedArchivesPath as NSString).appendingPathComponent(
notarizedArchiveBasename)
return notarizedArchive
}
} }

View File

@ -1,20 +1,27 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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 Cocoa;

View File

@ -1,20 +1,27 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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" #import "Chronosphere.h"
@ -27,15 +34,18 @@ BOOL appBundleChronoshiftedToARandomizedPath(NSString *bundle)
int entrySize = sizeof(struct statfs); int entrySize = sizeof(struct statfs);
struct statfs *bufs = (struct statfs *)calloc(entryCount, entrySize); struct statfs *bufs = (struct statfs *)calloc(entryCount, entrySize);
entryCount = getfsstat(bufs, entryCount * entrySize, MNT_NOWAIT); entryCount = getfsstat(bufs, entryCount * entrySize, MNT_NOWAIT);
for (int i = 0; i < entryCount; i++) { for (int i = 0; i < entryCount; i++)
if (!strcmp(bundleAbsPath, bufs[i].f_mntfromname)) { {
if (!strcmp(bundleAbsPath, bufs[i].f_mntfromname))
{
free(bufs); free(bufs);
// getfsstat() may return us a cached result, and so we need to get the stat of the mounted fs. // 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. // If statfs() returns an error, the mounted fs is already gone.
struct statfs stat; struct statfs stat;
int checkResult = statfs(bundleAbsPath, &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. // Meaning the app's bundle is not mounted, that is it's not translocated.
// It also means that the app is not loaded. // It also means that the app is not loaded.
return NO; return NO;

View File

@ -1,20 +1,27 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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.
*/ */
// //

View File

@ -1,20 +1,27 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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.
*/ */
// //

View File

@ -20,6 +20,22 @@ debug:
DSTROOT = /Library/Input Methods DSTROOT = /Library/Input Methods
VC_APP_ROOT = $(DSTROOT)/vChewing.app 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 .PHONY: permission-check install-debug install-release
permission-check: permission-check:
@ -37,3 +53,8 @@ clean:
xcodebuild -scheme vChewingInstaller -configuration Debug $(BUILD_SETTINGS) clean xcodebuild -scheme vChewingInstaller -configuration Debug $(BUILD_SETTINGS) clean
xcodebuild -scheme vChewingInstaller -configuration Release $(BUILD_SETTINGS) clean xcodebuild -scheme vChewingInstaller -configuration Release $(BUILD_SETTINGS) clean
make clean --file=./Source/Data/Makefile || true make clean --file=./Source/Data/Makefile || true
.PHONY: gc
gc:
git reflog expire --expire=now --all && git gc --prune=now --aggressive

View File

@ -3,83 +3,83 @@
import PackageDescription import PackageDescription
let package = Package( let package = Package(
name: "SwiftyOpenCC", name: "SwiftyOpenCC",
products: [ products: [
.library( .library(
name: "OpenCC", name: "OpenCC",
targets: ["OpenCC"]), targets: ["OpenCC"])
], ],
targets: [ targets: [
.target( .target(
name: "OpenCC", name: "OpenCC",
dependencies: ["copencc"], dependencies: ["copencc"],
resources: [ resources: [
.copy("Dictionary") .copy("Dictionary")
]), ]),
.testTarget( .testTarget(
name: "OpenCCTests", name: "OpenCCTests",
dependencies: ["OpenCC"], dependencies: ["OpenCC"],
resources: [ resources: [
.copy("benchmark"), .copy("benchmark"),
.copy("testcases"), .copy("testcases"),
]), ]),
.target( .target(
name: "copencc", name: "copencc",
exclude: [ exclude: [
"src/benchmark", "src/benchmark",
"src/tools", "src/tools",
"src/BinaryDictTest.cpp", "src/BinaryDictTest.cpp",
"src/Config.cpp", "src/Config.cpp",
"src/ConfigTest.cpp", "src/ConfigTest.cpp",
"src/ConversionChainTest.cpp", "src/ConversionChainTest.cpp",
"src/ConversionTest.cpp", "src/ConversionTest.cpp",
"src/DartsDictTest.cpp", "src/DartsDictTest.cpp",
"src/DictGroupTest.cpp", "src/DictGroupTest.cpp",
"src/MarisaDictTest.cpp", "src/MarisaDictTest.cpp",
"src/MaxMatchSegmentationTest.cpp", "src/MaxMatchSegmentationTest.cpp",
"src/PhraseExtractTest.cpp", "src/PhraseExtractTest.cpp",
"src/SerializedValuesTest.cpp", "src/SerializedValuesTest.cpp",
"src/SimpleConverter.cpp", "src/SimpleConverter.cpp",
"src/SimpleConverterTest.cpp", "src/SimpleConverterTest.cpp",
"src/TextDictTest.cpp", "src/TextDictTest.cpp",
"src/UTF8StringSliceTest.cpp", "src/UTF8StringSliceTest.cpp",
"src/UTF8UtilTest.cpp", "src/UTF8UtilTest.cpp",
"deps/google-benchmark", "deps/google-benchmark",
"deps/gtest-1.11.0", "deps/gtest-1.11.0",
"deps/pybind11-2.5.0", "deps/pybind11-2.5.0",
"deps/rapidjson-1.1.0", "deps/rapidjson-1.1.0",
"deps/tclap-1.2.2", "deps/tclap-1.2.2",
"src/CmdLineOutput.hpp", "src/CmdLineOutput.hpp",
"src/Config.hpp", "src/Config.hpp",
"src/ConfigTestBase.hpp", "src/ConfigTestBase.hpp",
"src/DictGroupTestBase.hpp", "src/DictGroupTestBase.hpp",
"src/SimpleConverter.hpp", "src/SimpleConverter.hpp",
"src/TestUtils.hpp", "src/TestUtils.hpp",
"src/TestUtilsUTF8.hpp", "src/TestUtilsUTF8.hpp",
"src/TextDictTestBase.hpp", "src/TextDictTestBase.hpp",
"src/py_opencc.cpp", "src/py_opencc.cpp",
// ??? // ???
"src/README.md", "src/README.md",
"src/CMakeLists.txt", "src/CMakeLists.txt",
"deps/marisa-0.2.6/AUTHORS", "deps/marisa-0.2.6/AUTHORS",
"deps/marisa-0.2.6/CMakeLists.txt", "deps/marisa-0.2.6/CMakeLists.txt",
"deps/marisa-0.2.6/COPYING.md", "deps/marisa-0.2.6/COPYING.md",
"deps/marisa-0.2.6/README.md", "deps/marisa-0.2.6/README.md",
], ],
sources: [ sources: [
"source.cpp", "source.cpp",
"src", "src",
"deps/marisa-0.2.6", "deps/marisa-0.2.6",
], ],
cxxSettings: [ cxxSettings: [
.headerSearchPath("src"), .headerSearchPath("src"),
.headerSearchPath("deps/darts-clone"), .headerSearchPath("deps/darts-clone"),
.headerSearchPath("deps/marisa-0.2.6/include"), .headerSearchPath("deps/marisa-0.2.6/include"),
.headerSearchPath("deps/marisa-0.2.6/lib"), .headerSearchPath("deps/marisa-0.2.6/lib"),
.define("ENABLE_DARTS"), .define("ENABLE_DARTS"),
]), ]),
], ],
cxxLanguageStandard: .cxx14 cxxLanguageStandard: .cxx14
) )

View File

@ -22,68 +22,68 @@ import copencc
/// However, the string on which it is operating should not be mutated /// However, the string on which it is operating should not be mutated
/// during the course of a conversion. /// during the course of a conversion.
public class ChineseConverter { 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. /// These constants define the ChineseConverter options.
public static let twIdiom = Options(rawValue: 1 << 10) public struct Options: OptionSet {
}
public let rawValue: Int
private let seg: ConversionDictionary
private let chain: [ConversionDictionary] public init(rawValue: Int) {
self.rawValue = rawValue
private let converter: CCConverterRef }
private init(loader: DictionaryLoader, options: Options) throws { /// Convert to Traditional Chinese. (default)
seg = try loader.segmentation(options: options) public static let traditionalize = Options(rawValue: 1 << 0)
chain = try loader.conversionChain(options: options)
var rawChain = chain.map { $0.dict } /// Convert to Simplified Chinese.
converter = CCConverterCreate("SwiftyOpenCC", seg.dict, &rawChain, rawChain.count) public static let simplify = Options(rawValue: 1 << 1)
}
/// Use Taiwan standard.
/// Returns an initialized `ChineseConverter` instance with the specified public static let twStandard = Options(rawValue: 1 << 5)
/// conversion options.
/// /// Use HongKong standard.
/// - Parameter options: The converts options. public static let hkStandard = Options(rawValue: 1 << 6)
/// - Throws: Throws `ConversionError` if failed.
public convenience init(options: Options) throws { /// Cancel Taiwan standard.
let loader = DictionaryLoader(bundle: .module) public static let twStandardRev = Options(rawValue: 1 << 15)
try self.init(loader: loader, options: options)
} /// Cancel HongKong standard.
public static let hkStandardRev = Options(rawValue: 1 << 16)
/// Return a converted string using the converts current option.
/// /// Taiwanese idiom conversion.
/// - Parameter text: The string to convert. public static let twIdiom = Options(rawValue: 1 << 10)
/// - Returns: A converted string using the converts current option. }
public func convert(_ text: String) -> String {
let stlStr = CCConverterCreateConvertedStringFromString(converter, text)! private let seg: ConversionDictionary
defer { STLStringDestroy(stlStr) } private let chain: [ConversionDictionary]
return String(utf8String: STLStringGetUTF8String(stlStr))!
} 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 converts 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 converts current option.
///
/// - Parameter text: The string to convert.
/// - Returns: A converted string using the converts current option.
public func convert(_ text: String) -> String {
let stlStr = CCConverterCreateConvertedStringFromString(converter, text)!
defer { STLStringDestroy(stlStr) }
return String(utf8String: STLStringGetUTF8String(stlStr))!
}
} }

View File

@ -9,22 +9,22 @@ import Foundation
import copencc import copencc
class ConversionDictionary { class ConversionDictionary {
let group: [ConversionDictionary] let group: [ConversionDictionary]
let dict: CCDictRef let dict: CCDictRef
init(path: String) throws { init(path: String) throws {
guard let dict = CCDictCreateMarisaWithPath(path) else { guard let dict = CCDictCreateMarisaWithPath(path) else {
throw ConversionError(ccErrorno) throw ConversionError(ccErrorno)
} }
self.group = [] self.group = []
self.dict = dict self.dict = dict
} }
init(group: [ConversionDictionary]) { init(group: [ConversionDictionary]) {
var rawGroup = group.map { $0.dict } var rawGroup = group.map { $0.dict }
self.group = group self.group = group
self.dict = CCDictCreateWithGroup(&rawGroup, rawGroup.count) self.dict = CCDictCreateWithGroup(&rawGroup, rawGroup.count)
} }
} }

View File

@ -9,29 +9,29 @@ import Foundation
import copencc import copencc
public enum ConversionError: Error { public enum ConversionError: Error {
case fileNotFound case fileNotFound
case invalidFormat case invalidFormat
case invalidTextDictionary case invalidTextDictionary
case invalidUTF8 case invalidUTF8
case unknown case unknown
init(_ code: CCErrorCode) { init(_ code: CCErrorCode) {
switch code { switch code {
case .fileNotFound: case .fileNotFound:
self = .fileNotFound self = .fileNotFound
case .invalidFormat: case .invalidFormat:
self = .invalidFormat self = .invalidFormat
case .invalidTextDictionary: case .invalidTextDictionary:
self = .invalidTextDictionary self = .invalidTextDictionary
case .invalidUTF8: case .invalidUTF8:
self = .invalidUTF8 self = .invalidUTF8
case .unknown, _: case .unknown, _:
self = .unknown self = .unknown
} }
} }
} }

View File

@ -9,47 +9,51 @@ import Foundation
import copencc import copencc
extension ChineseConverter { extension ChineseConverter {
struct DictionaryLoader { struct DictionaryLoader {
private static let subdirectory = "Dictionary" private static let subdirectory = "Dictionary"
private static let dictCache = WeakValueCache<String, ConversionDictionary>() private static let dictCache = WeakValueCache<String, ConversionDictionary>()
private let bundle: Bundle private let bundle: Bundle
init(bundle: Bundle) { init(bundle: Bundle) {
self.bundle = bundle self.bundle = bundle
} }
func dict(_ name: ChineseConverter.DictionaryName) throws -> ConversionDictionary { func dict(_ name: ChineseConverter.DictionaryName) throws -> ConversionDictionary {
guard let path = bundle.path(forResource: name.description, ofType: "ocd2", inDirectory: DictionaryLoader.subdirectory) else { guard
throw ConversionError.fileNotFound let path = bundle.path(
} forResource: name.description, ofType: "ocd2",
return try DictionaryLoader.dictCache.value(for: path) { inDirectory: DictionaryLoader.subdirectory)
return try ConversionDictionary(path: path) else {
} throw ConversionError.fileNotFound
} }
} return try DictionaryLoader.dictCache.value(for: path) {
return try ConversionDictionary(path: path)
}
}
}
} }
extension ChineseConverter.DictionaryLoader { extension ChineseConverter.DictionaryLoader {
func segmentation(options: ChineseConverter.Options) throws -> ConversionDictionary { func segmentation(options: ChineseConverter.Options) throws -> ConversionDictionary {
let dictName = options.segmentationDictName let dictName = options.segmentationDictName
return try dict(dictName) return try dict(dictName)
} }
func conversionChain(options: ChineseConverter.Options) throws -> [ConversionDictionary] { func conversionChain(options: ChineseConverter.Options) throws -> [ConversionDictionary] {
return try options.conversionChain.compactMap { names in return try options.conversionChain.compactMap { names in
switch names.count { switch names.count {
case 0: case 0:
return nil return nil
case 1: case 1:
return try dict(names.first!) return try dict(names.first!)
case _: case _:
let dicts = try names.map(dict) let dicts = try names.map(dict)
return ConversionDictionary(group: dicts) return ConversionDictionary(group: dicts)
} }
} }
} }
} }

View File

@ -8,99 +8,99 @@
import Foundation import Foundation
extension ChineseConverter { extension ChineseConverter {
enum DictionaryName: CustomStringConvertible { enum DictionaryName: CustomStringConvertible {
case hkVariants case hkVariants
case hkVariantsRev case hkVariantsRev
case hkVariantsRevPhrases case hkVariantsRevPhrases
case jpVariants case jpVariants
case stCharacters case stCharacters
case stPhrases case stPhrases
case tsCharacters case tsCharacters
case tsPhrases case tsPhrases
case twPhrases case twPhrases
case twPhrasesRev case twPhrasesRev
case twVariants case twVariants
case twVariantsRev case twVariantsRev
case twVariantsRevPhrases case twVariantsRevPhrases
var description: String { var description: String {
switch self { switch self {
case .hkVariants: return "HKVariants" case .hkVariants: return "HKVariants"
case .hkVariantsRev: return "HKVariantsRev" case .hkVariantsRev: return "HKVariantsRev"
case .hkVariantsRevPhrases: return "HKVariantsRevPhrases" case .hkVariantsRevPhrases: return "HKVariantsRevPhrases"
case .jpVariants: return "JPVariants" case .jpVariants: return "JPVariants"
case .stCharacters: return "STCharacters" case .stCharacters: return "STCharacters"
case .stPhrases: return "STPhrases" case .stPhrases: return "STPhrases"
case .tsCharacters: return "TSCharacters" case .tsCharacters: return "TSCharacters"
case .tsPhrases: return "TSPhrases" case .tsPhrases: return "TSPhrases"
case .twPhrases: return "TWPhrases" case .twPhrases: return "TWPhrases"
case .twPhrasesRev: return "TWPhrasesRev" case .twPhrasesRev: return "TWPhrasesRev"
case .twVariants: return "TWVariants" case .twVariants: return "TWVariants"
case .twVariantsRev: return "TWVariantsRev" case .twVariantsRev: return "TWVariantsRev"
case .twVariantsRevPhrases: return "TWVariantsRevPhrases" case .twVariantsRevPhrases: return "TWVariantsRevPhrases"
} }
} }
} }
} }
extension ChineseConverter.Options { extension ChineseConverter.Options {
var segmentationDictName: ChineseConverter.DictionaryName { var segmentationDictName: ChineseConverter.DictionaryName {
if contains(.traditionalize) { if contains(.traditionalize) {
return .stPhrases return .stPhrases
} else if contains(.simplify) { } else if contains(.simplify) {
return .tsPhrases return .tsPhrases
} else if contains(.hkStandard) { } else if contains(.hkStandard) {
return .hkVariants return .hkVariants
} else if contains(.twStandard) { } else if contains(.twStandard) {
return .twVariants return .twVariants
} else if contains(.hkStandardRev) { } else if contains(.hkStandardRev) {
return .hkVariantsRev return .hkVariantsRev
} else if contains(.twStandardRev) { } else if contains(.twStandardRev) {
return .twVariantsRev return .twVariantsRev
} else { } else {
return .stPhrases return .stPhrases
} }
} }
var conversionChain: [[ChineseConverter.DictionaryName]] { var conversionChain: [[ChineseConverter.DictionaryName]] {
var result: [[ChineseConverter.DictionaryName]] = [] var result: [[ChineseConverter.DictionaryName]] = []
if contains(.traditionalize) { if contains(.traditionalize) {
result.append([.stPhrases, .stCharacters]) result.append([.stPhrases, .stCharacters])
if contains(.twIdiom) { if contains(.twIdiom) {
result.append([.twPhrases]) result.append([.twPhrases])
} }
if contains(.hkStandard) { if contains(.hkStandard) {
result.append([.hkVariants]) result.append([.hkVariants])
} else if contains(.twStandard) { } else if contains(.twStandard) {
result.append([.twVariants]) result.append([.twVariants])
} }
} else if contains(.simplify) { } else if contains(.simplify) {
if contains(.hkStandard) { if contains(.hkStandard) {
result.append([.hkVariantsRevPhrases, .hkVariantsRev]) result.append([.hkVariantsRevPhrases, .hkVariantsRev])
} else if contains(.twStandard) { } else if contains(.twStandard) {
result.append([.twVariantsRevPhrases, .twVariantsRev]) result.append([.twVariantsRevPhrases, .twVariantsRev])
} }
if contains(.twIdiom) { if contains(.twIdiom) {
result.append([.twPhrasesRev]) result.append([.twPhrasesRev])
} }
result.append([.tsPhrases, .tsCharacters]) result.append([.tsPhrases, .tsCharacters])
} else { } else {
if contains(.hkStandard) { if contains(.hkStandard) {
result.append([.hkVariants]) result.append([.hkVariants])
} else if contains(.twStandard) { } else if contains(.twStandard) {
result.append([.twVariants]) result.append([.twVariants])
} else if contains(.hkStandardRev) { } else if contains(.hkStandardRev) {
result.append([.hkVariantsRev]) result.append([.hkVariantsRev])
} else if contains(.twStandardRev) { } else if contains(.twStandardRev) {
result.append([.twVariantsRev]) result.append([.twVariantsRev])
} }
} }
if result.isEmpty { if result.isEmpty {
return [[.stPhrases, .stCharacters]] return [[.stPhrases, .stCharacters]]
} }
return result return result
} }
} }

View File

@ -8,35 +8,35 @@
import Foundation import Foundation
class WeakBox<Value: AnyObject> { class WeakBox<Value: AnyObject> {
private(set) weak var value: Value? private(set) weak var value: Value?
init(_ value: Value) { init(_ value: Value) {
self.value = value self.value = value
} }
} }
class WeakValueCache<Key: Hashable, Value: AnyObject> { class WeakValueCache<Key: Hashable, Value: AnyObject> {
private var storage: [Key: WeakBox<Value>] = [:] private var storage: [Key: WeakBox<Value>] = [:]
private var lock = NSLock() private var lock = NSLock()
func value(for key: Key) -> Value? { func value(for key: Key) -> Value? {
return storage[key]?.value return storage[key]?.value
} }
func value(for key: Key, make: () throws -> Value) rethrows -> Value { func value(for key: Key, make: () throws -> Value) rethrows -> Value {
if let value = storage[key]?.value { if let value = storage[key]?.value {
return value return value
} }
lock.lock() lock.lock()
defer { lock.unlock() } defer { lock.unlock() }
if let value = storage[key]?.value { if let value = storage[key]?.value {
return value return value
} }
let value = try make() let value = try make()
storage[key] = WeakBox(value) storage[key] = WeakBox(value)
return value return value
} }
} }

View File

@ -1,64 +1,67 @@
import XCTest import XCTest
@testable import OpenCC @testable import OpenCC
let testCases: [(String, ChineseConverter.Options)] = [ let testCases: [(String, ChineseConverter.Options)] = [
("s2t", [.traditionalize]), ("s2t", [.traditionalize]),
("t2s", [.simplify]), ("t2s", [.simplify]),
("s2hk", [.traditionalize, .hkStandard]), ("s2hk", [.traditionalize, .hkStandard]),
("hk2s", [.simplify, .hkStandard]), ("hk2s", [.simplify, .hkStandard]),
("s2tw", [.traditionalize, .twStandard]), ("s2tw", [.traditionalize, .twStandard]),
("tw2s", [.simplify, .twStandard]), ("tw2s", [.simplify, .twStandard]),
("s2twp", [.traditionalize, .twStandard, .twIdiom]), ("s2twp", [.traditionalize, .twStandard, .twIdiom]),
("tw2sp", [.simplify, .twStandard, .twIdiom]), ("tw2sp", [.simplify, .twStandard, .twIdiom]),
] ]
class OpenCCTests: XCTestCase { class OpenCCTests: XCTestCase {
func converter(option: ChineseConverter.Options) throws -> ChineseConverter { func converter(option: ChineseConverter.Options) throws -> ChineseConverter {
return try ChineseConverter(options: option) return try ChineseConverter(options: option)
} }
func testConversion() throws { func testConversion() throws {
func testCase(name: String, ext: String) -> String { func testCase(name: String, ext: String) -> String {
let url = Bundle.module.url(forResource: name, withExtension: ext, subdirectory: "testcases")! let url = Bundle.module.url(
return try! String(contentsOf: url) forResource: name, withExtension: ext, subdirectory: "testcases")!
} return try! String(contentsOf: url)
for (name, opt) in testCases { }
let coverter = try ChineseConverter(options: opt) for (name, opt) in testCases {
let input = testCase(name: name, ext: "in") let coverter = try ChineseConverter(options: opt)
let converted = coverter.convert(input) let input = testCase(name: name, ext: "in")
let output = testCase(name: name, ext: "ans") let converted = coverter.convert(input)
XCTAssertEqual(converted, output, "Conversion \(name) fails") let output = testCase(name: name, ext: "ans")
} XCTAssertEqual(converted, output, "Conversion \(name) fails")
} }
}
func testConverterCreationPerformance() {
let options: ChineseConverter.Options = [.traditionalize, .twStandard, .twIdiom] func testConverterCreationPerformance() {
measure { let options: ChineseConverter.Options = [.traditionalize, .twStandard, .twIdiom]
for _ in 0..<10 { measure {
_ = try! ChineseConverter(options: options) for _ in 0..<10 {
} _ = try! ChineseConverter(options: options)
} }
} }
}
func testDictionaryCache() {
let options: ChineseConverter.Options = [.traditionalize, .twStandard, .twIdiom] func testDictionaryCache() {
let holder = try! ChineseConverter(options: options) let options: ChineseConverter.Options = [.traditionalize, .twStandard, .twIdiom]
measure { let holder = try! ChineseConverter(options: options)
for _ in 0..<1_000 { measure {
_ = try! ChineseConverter(options: options) for _ in 0..<1_000 {
} _ = try! ChineseConverter(options: options)
} }
_ = holder.convert("foo") }
} _ = holder.convert("foo")
}
func testConversionPerformance() throws {
let cov = try converter(option: [.traditionalize, .twStandard, .twIdiom]) func testConversionPerformance() throws {
let url = Bundle.module.url(forResource: "zuozhuan", withExtension: "txt", subdirectory: "benchmark")! let cov = try converter(option: [.traditionalize, .twStandard, .twIdiom])
// 1.9 MB, 624k word let url = Bundle.module.url(
let str = try String(contentsOf: url) forResource: "zuozhuan", withExtension: "txt", subdirectory: "benchmark")!
measure { // 1.9 MB, 624k word
_ = cov.convert(str) let str = try String(contentsOf: url)
} measure {
} _ = cov.convert(str)
}
}
} }

View File

@ -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 行文規範。該規範以四個西文半形空格為行縮進單位。
## 特殊勸告 ## 特殊勸告
為了您的精神衛生,任何使用威注音輸入法時遇到的產品問題、請勿提報至小麥注音,除非您確信小麥注音也有該問題。即便如此,也請在他們那邊不要提及威注音。 為了您的精神衛生,任何使用威注音輸入法時遇到的產品問題、請勿提報至小麥注音,除非您確信小麥注音也有該問題。即便如此,也請在他們那邊不要提及威注音。

File diff suppressed because it is too large Load Diff

View File

@ -1,20 +1,27 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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_ #ifndef MANDARIN_H_
@ -25,79 +32,115 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR TH
#include <string> #include <string>
#include <vector> #include <vector>
namespace Mandarin { namespace Mandarin
{
class BopomofoSyllable { class BopomofoSyllable
public: {
public:
typedef uint16_t Component; typedef uint16_t Component;
explicit BopomofoSyllable(Component syllable = 0) : syllable_(syllable) {} explicit BopomofoSyllable(Component syllable = 0) : syllable_(syllable)
{
BopomofoSyllable(const BopomofoSyllable&) = default; }
BopomofoSyllable(BopomofoSyllable&& another) = default;
BopomofoSyllable& operator=(const BopomofoSyllable&) = default; BopomofoSyllable(const BopomofoSyllable &) = default;
BopomofoSyllable& operator=(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 // takes the ASCII-form, "v"-tolerant, TW-style Hanyu Pinyin (fong, pong, bong
// acceptable) // acceptable)
static const BopomofoSyllable FromHanyuPinyin(const std::string& str); static const BopomofoSyllable FromHanyuPinyin(const std::string &str);
// TO DO: Support accented vowels // TO DO: Support accented vowels
const std::string HanyuPinyinString(bool includesTone, const std::string HanyuPinyinString(bool includesTone, bool useVForUUmlaut) const;
bool useVForUUmlaut) const;
static const BopomofoSyllable FromComposedString(const std::string &str);
static const BopomofoSyllable FromComposedString(const std::string& str);
const std::string composedString() const; const std::string composedString() const;
void clear() { syllable_ = 0; } void clear()
{
bool isEmpty() const { return !syllable_; } syllable_ = 0;
}
bool hasConsonant() const { return !!(syllable_ & ConsonantMask); }
bool isEmpty() const
bool hasMiddleVowel() const { return !!(syllable_ & MiddleVowelMask); } {
bool hasVowel() const { return !!(syllable_ & VowelMask); } return !syllable_;
}
bool hasToneMarker() const { return !!(syllable_ & ToneMarkerMask); }
bool hasConsonant() const
Component consonantComponent() const { return syllable_ & ConsonantMask; } {
return !!(syllable_ & ConsonantMask);
Component middleVowelComponent() const { }
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; return syllable_ & MiddleVowelMask;
} }
Component vowelComponent() const { return syllable_ & VowelMask; } Component vowelComponent() const
{
Component toneMarkerComponent() const { return syllable_ & ToneMarkerMask; } return syllable_ & VowelMask;
}
bool operator==(const BopomofoSyllable& another) const {
Component toneMarkerComponent() const
{
return syllable_ & ToneMarkerMask;
}
bool operator==(const BopomofoSyllable &another) const
{
return syllable_ == another.syllable_; return syllable_ == another.syllable_;
} }
bool operator!=(const BopomofoSyllable& another) const { bool operator!=(const BopomofoSyllable &another) const
{
return syllable_ != another.syllable_; 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)) #define IOW_SAND(mask) ((syllable_ & mask) && (another.syllable_ & mask))
return IOW_SAND(ConsonantMask) || IOW_SAND(MiddleVowelMask) || return IOW_SAND(ConsonantMask) || IOW_SAND(MiddleVowelMask) || IOW_SAND(VowelMask) || IOW_SAND(ToneMarkerMask);
IOW_SAND(VowelMask) || IOW_SAND(ToneMarkerMask);
#undef IOW_SAND #undef IOW_SAND
} }
// consonants J, Q, X all require the existence of vowel I or UE // consonants J, Q, X all require the existence of vowel I or UE
bool belongsToJQXClass() const { bool belongsToJQXClass() const
{
Component consonant = syllable_ & ConsonantMask; Component consonant = syllable_ & ConsonantMask;
return (consonant == J || consonant == Q || consonant == X); return (consonant == J || consonant == Q || consonant == X);
} }
// zi, ci, si, chi, chi, shi, ri // zi, ci, si, chi, chi, shi, ri
bool belongsToZCSRClass() const { bool belongsToZCSRClass() const
{
Component consonant = syllable_ & ConsonantMask; Component consonant = syllable_ & ConsonantMask;
return (consonant >= ZH && consonant <= S); return (consonant >= ZH && consonant <= S);
} }
Component maskType() const { Component maskType() const
{
Component mask = 0; Component mask = 0;
mask |= (syllable_ & ConsonantMask) ? ConsonantMask : 0; mask |= (syllable_ & ConsonantMask) ? ConsonantMask : 0;
mask |= (syllable_ & MiddleVowelMask) ? MiddleVowelMask : 0; mask |= (syllable_ & MiddleVowelMask) ? MiddleVowelMask : 0;
@ -105,13 +148,15 @@ public:
mask |= (syllable_ & ToneMarkerMask) ? ToneMarkerMask : 0; mask |= (syllable_ & ToneMarkerMask) ? ToneMarkerMask : 0;
return mask; return mask;
} }
const BopomofoSyllable operator+(const BopomofoSyllable& another) const { const BopomofoSyllable operator+(const BopomofoSyllable &another) const
{
Component newSyllable = syllable_; Component newSyllable = syllable_;
#define OP_SOVER(mask) \ #define OP_SOVER(mask) \
if (another.syllable_ & mask) { \ if (another.syllable_ & mask) \
newSyllable = (newSyllable & ~mask) | (another.syllable_ & mask); \ { \
} newSyllable = (newSyllable & ~mask) | (another.syllable_ & mask); \
}
OP_SOVER(ConsonantMask); OP_SOVER(ConsonantMask);
OP_SOVER(MiddleVowelMask); OP_SOVER(MiddleVowelMask);
OP_SOVER(VowelMask); OP_SOVER(VowelMask);
@ -119,12 +164,14 @@ newSyllable = (newSyllable & ~mask) | (another.syllable_ & mask); \
#undef OP_SOVER #undef OP_SOVER
return BopomofoSyllable(newSyllable); return BopomofoSyllable(newSyllable);
} }
BopomofoSyllable& operator+=(const BopomofoSyllable& another) { BopomofoSyllable &operator+=(const BopomofoSyllable &another)
#define OPE_SOVER(mask) \ {
if (another.syllable_ & mask) { \ #define OPE_SOVER(mask) \
syllable_ = (syllable_ & ~mask) | (another.syllable_ & mask); \ if (another.syllable_ & mask) \
} { \
syllable_ = (syllable_ & ~mask) | (another.syllable_ & mask); \
}
OPE_SOVER(ConsonantMask); OPE_SOVER(ConsonantMask);
OPE_SOVER(MiddleVowelMask); OPE_SOVER(MiddleVowelMask);
OPE_SOVER(VowelMask); OPE_SOVER(VowelMask);
@ -132,87 +179,88 @@ syllable_ = (syllable_ & ~mask) | (another.syllable_ & mask); \
#undef OPE_SOVER #undef OPE_SOVER
return *this; return *this;
} }
friend std::ostream& operator<<(std::ostream& stream, friend std::ostream &operator<<(std::ostream &stream, const BopomofoSyllable &syllable);
const BopomofoSyllable& syllable);
static constexpr Component ConsonantMask = 0x001f, // 0000 0000 0001 1111, 21 consonants
static constexpr Component MiddleVowelMask = 0x0060, // 0000 0000 0110 0000, 3 middle vowels
ConsonantMask = 0x001f, // 0000 0000 0001 1111, 21 consonants VowelMask = 0x0780, // 0000 0111 1000 0000, 13 vowels
MiddleVowelMask = 0x0060, // 0000 0000 0110 0000, 3 middle vowels ToneMarkerMask = 0x3800, // 0011 1000 0000 0000, 5 tones (tone1 = 0x00)
VowelMask = 0x0780, // 0000 0111 1000 0000, 13 vowels B = 0x0001, P = 0x0002, M = 0x0003, F = 0x0004, D = 0x0005, T = 0x0006, N = 0x0007, L = 0x0008, G = 0x0009,
ToneMarkerMask = 0x3800, // 0011 1000 0000 0000, 5 tones (tone1 = 0x00) K = 0x000a, H = 0x000b, J = 0x000c, Q = 0x000d, X = 0x000e, ZH = 0x000f, CH = 0x0010,
B = 0x0001, P = 0x0002, M = 0x0003, F = 0x0004, D = 0x0005, T = 0x0006, SH = 0x0011, R = 0x0012, Z = 0x0013, C = 0x0014, S = 0x0015, I = 0x0020, U = 0x0040,
N = 0x0007, L = 0x0008, G = 0x0009, K = 0x000a, H = 0x000b, J = 0x000c, UE = 0x0060, // ue = u umlaut (we use the German convention here as an
Q = 0x000d, X = 0x000e, ZH = 0x000f, CH = 0x0010, SH = 0x0011, R = 0x0012, // ersatz to the /ju:/ sound)
Z = 0x0013, C = 0x0014, S = 0x0015, I = 0x0020, U = 0x0040, A = 0x0080, O = 0x0100, ER = 0x0180, E = 0x0200, AI = 0x0280, EI = 0x0300, AO = 0x0380, OU = 0x0400,
UE = 0x0060, // ue = u umlaut (we use the German convention here as an AN = 0x0480, EN = 0x0500, ANG = 0x0580, ENG = 0x0600, ERR = 0x0680, Tone1 = 0x0000,
// ersatz to the /ju:/ sound) Tone2 = 0x0800, Tone3 = 0x1000, Tone4 = 0x1800, Tone5 = 0x2000;
A = 0x0080, O = 0x0100, ER = 0x0180, E = 0x0200, AI = 0x0280, EI = 0x0300,
AO = 0x0380, OU = 0x0400, AN = 0x0480, EN = 0x0500, ANG = 0x0580, protected:
ENG = 0x0600, ERR = 0x0680, Tone1 = 0x0000, Tone2 = 0x0800,
Tone3 = 0x1000, Tone4 = 0x1800, Tone5 = 0x2000;
protected:
Component syllable_; Component syllable_;
}; };
inline std::ostream& operator<<(std::ostream& stream, inline std::ostream &operator<<(std::ostream &stream, const BopomofoSyllable &syllable)
const BopomofoSyllable& syllable) { {
stream << syllable.composedString(); stream << syllable.composedString();
return stream; return stream;
} }
typedef BopomofoSyllable BPMF; typedef BopomofoSyllable BPMF;
typedef std::map<char, std::vector<BPMF::Component> > BopomofoKeyToComponentMap; typedef std::map<char, std::vector<BPMF::Component>> BopomofoKeyToComponentMap;
typedef std::map<BPMF::Component, char> BopomofoComponentToKeyMap; typedef std::map<BPMF::Component, char> BopomofoComponentToKeyMap;
class BopomofoKeyboardLayout { class BopomofoKeyboardLayout
public: {
static const BopomofoKeyboardLayout* StandardLayout(); public:
static const BopomofoKeyboardLayout* ETenLayout(); static const BopomofoKeyboardLayout *StandardLayout();
static const BopomofoKeyboardLayout* HsuLayout(); static const BopomofoKeyboardLayout *ETenLayout();
static const BopomofoKeyboardLayout* ETen26Layout(); static const BopomofoKeyboardLayout *HsuLayout();
static const BopomofoKeyboardLayout* IBMLayout(); static const BopomofoKeyboardLayout *ETen26Layout();
static const BopomofoKeyboardLayout* MiTACLayout(); static const BopomofoKeyboardLayout *IBMLayout();
static const BopomofoKeyboardLayout* FakeSeigyouLayout(); static const BopomofoKeyboardLayout *MiTACLayout();
static const BopomofoKeyboardLayout* HanyuPinyinLayout(); static const BopomofoKeyboardLayout *FakeSeigyouLayout();
static const BopomofoKeyboardLayout *HanyuPinyinLayout();
BopomofoKeyboardLayout(const BopomofoKeyToComponentMap& ktcm,
const std::string& name) BopomofoKeyboardLayout(const BopomofoKeyToComponentMap &ktcm, const std::string &name)
: m_keyToComponent(ktcm), m_name(name) { : m_keyToComponent(ktcm), m_name(name)
for (BopomofoKeyToComponentMap::const_iterator miter = {
m_keyToComponent.begin(); for (BopomofoKeyToComponentMap::const_iterator miter = m_keyToComponent.begin();
miter != m_keyToComponent.end(); ++miter) miter != m_keyToComponent.end(); ++miter)
for (std::vector<BPMF::Component>::const_iterator viter = for (std::vector<BPMF::Component>::const_iterator viter = (*miter).second.begin();
(*miter).second.begin();
viter != (*miter).second.end(); ++viter) viter != (*miter).second.end(); ++viter)
m_componentToKey[*viter] = (*miter).first; m_componentToKey[*viter] = (*miter).first;
} }
const std::string name() const { return m_name; } const std::string name() const
{
char componentToKey(BPMF::Component component) const { return m_name;
BopomofoComponentToKeyMap::const_iterator iter = }
m_componentToKey.find(component);
char componentToKey(BPMF::Component component) const
{
BopomofoComponentToKeyMap::const_iterator iter = m_componentToKey.find(component);
return (iter == m_componentToKey.end()) ? 0 : (*iter).second; return (iter == m_componentToKey.end()) ? 0 : (*iter).second;
} }
const std::vector<BPMF::Component> keyToComponents(char key) const { const std::vector<BPMF::Component> keyToComponents(char key) const
{
BopomofoKeyToComponentMap::const_iterator iter = m_keyToComponent.find(key); BopomofoKeyToComponentMap::const_iterator iter = m_keyToComponent.find(key);
return (iter == m_keyToComponent.end()) ? std::vector<BPMF::Component>() return (iter == m_keyToComponent.end()) ? std::vector<BPMF::Component>() : (*iter).second;
: (*iter).second;
} }
const std::string keySequenceFromSyllable(BPMF syllable) const { const std::string keySequenceFromSyllable(BPMF syllable) const
{
std::string sequence; std::string sequence;
BPMF::Component c; BPMF::Component c;
char k; char k;
#define STKS_COMBINE(component) \ #define STKS_COMBINE(component) \
if ((c = component)) { \ if ((c = component)) \
if ((k = componentToKey(c))) sequence += std::string(1, k); \ { \
} if ((k = componentToKey(c))) \
sequence += std::string(1, k); \
}
STKS_COMBINE(syllable.consonantComponent()); STKS_COMBINE(syllable.consonantComponent());
STKS_COMBINE(syllable.middleVowelComponent()); STKS_COMBINE(syllable.middleVowelComponent());
STKS_COMBINE(syllable.vowelComponent()); STKS_COMBINE(syllable.vowelComponent());
@ -220,256 +268,314 @@ if ((k = componentToKey(c))) sequence += std::string(1, k); \
#undef STKS_COMBINE #undef STKS_COMBINE
return sequence; return sequence;
} }
const BPMF syllableFromKeySequence(const std::string& sequence) const { const BPMF syllableFromKeySequence(const std::string &sequence) const
{
BPMF syllable; BPMF syllable;
for (std::string::const_iterator iter = sequence.begin(); for (std::string::const_iterator iter = sequence.begin(); iter != sequence.end(); ++iter)
iter != sequence.end(); ++iter) { {
bool beforeSeqHasIorUE = sequenceContainsIorUE(sequence.begin(), iter); bool beforeSeqHasIorUE = sequenceContainsIorUE(sequence.begin(), iter);
bool aheadSeqHasIorUE = sequenceContainsIorUE(iter + 1, sequence.end()); bool aheadSeqHasIorUE = sequenceContainsIorUE(iter + 1, sequence.end());
std::vector<BPMF::Component> components = keyToComponents(*iter); std::vector<BPMF::Component> components = keyToComponents(*iter);
if (!components.size()) continue; if (!components.size())
continue;
if (components.size() == 1) {
if (components.size() == 1)
{
syllable += BPMF(components[0]); syllable += BPMF(components[0]);
continue; continue;
} }
BPMF head = BPMF(components[0]); BPMF head = BPMF(components[0]);
BPMF follow = BPMF(components[1]); BPMF follow = BPMF(components[1]);
BPMF ending = components.size() > 2 ? BPMF(components[2]) : follow; BPMF ending = components.size() > 2 ? BPMF(components[2]) : follow;
// apply the I/UE + E rule // apply the I/UE + E rule
if (head.vowelComponent() == BPMF::E && if (head.vowelComponent() == BPMF::E && follow.vowelComponent() != BPMF::E)
follow.vowelComponent() != BPMF::E) { {
syllable += beforeSeqHasIorUE ? head : follow; syllable += beforeSeqHasIorUE ? head : follow;
continue; continue;
} }
if (head.vowelComponent() != BPMF::E && if (head.vowelComponent() != BPMF::E && follow.vowelComponent() == BPMF::E)
follow.vowelComponent() == BPMF::E) { {
syllable += beforeSeqHasIorUE ? follow : head; syllable += beforeSeqHasIorUE ? follow : head;
continue; continue;
} }
// apply the J/Q/X + I/UE rule, only two components are allowed in the // apply the J/Q/X + I/UE rule, only two components are allowed in the
// components vector here // components vector here
if (head.belongsToJQXClass() && !follow.belongsToJQXClass()) { if (head.belongsToJQXClass() && !follow.belongsToJQXClass())
if (!syllable.isEmpty()) { {
if (ending != follow) syllable += ending; if (!syllable.isEmpty())
} else { {
if (ending != follow)
syllable += ending;
}
else
{
syllable += aheadSeqHasIorUE ? head : follow; syllable += aheadSeqHasIorUE ? head : follow;
} }
continue; continue;
} }
if (!head.belongsToJQXClass() && follow.belongsToJQXClass()) { if (!head.belongsToJQXClass() && follow.belongsToJQXClass())
if (!syllable.isEmpty()) { {
if (ending != follow) syllable += ending; if (!syllable.isEmpty())
} else { {
if (ending != follow)
syllable += ending;
}
else
{
syllable += aheadSeqHasIorUE ? follow : head; syllable += aheadSeqHasIorUE ? follow : head;
} }
continue; continue;
} }
// the nasty issue of only one char in the buffer // the nasty issue of only one char in the buffer
if (iter == sequence.begin() && iter + 1 == sequence.end()) { if (iter == sequence.begin() && iter + 1 == sequence.end())
if (head.hasVowel() || follow.hasToneMarker() || {
head.belongsToZCSRClass()) { if (head.hasVowel() || follow.hasToneMarker() || head.belongsToZCSRClass())
{
syllable += head; syllable += head;
} else { }
if (follow.hasVowel() || ending.hasToneMarker()) { else
{
if (follow.hasVowel() || ending.hasToneMarker())
{
syllable += follow; syllable += follow;
} else { }
else
{
syllable += ending; syllable += ending;
} }
} }
continue; continue;
} }
if (!(syllable.maskType() & head.maskType()) && if (!(syllable.maskType() & head.maskType()) && !endAheadOrAheadHasToneMarkKey(iter + 1, sequence.end()))
!endAheadOrAheadHasToneMarkKey(iter + 1, sequence.end())) { {
syllable += head; syllable += head;
} else { }
if (endAheadOrAheadHasToneMarkKey(iter + 1, sequence.end()) && else
head.belongsToZCSRClass() && syllable.isEmpty()) { {
if (endAheadOrAheadHasToneMarkKey(iter + 1, sequence.end()) && head.belongsToZCSRClass() &&
syllable.isEmpty())
{
syllable += head; syllable += head;
} else if (syllable.maskType() < follow.maskType()) { }
else if (syllable.maskType() < follow.maskType())
{
syllable += follow; syllable += follow;
} else { }
else
{
syllable += ending; syllable += ending;
} }
} }
} }
// heuristics for Hsu keyboard layout // 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 // fix the left out L to ERR when it has sound, and GI, GUE -> JI, JUE
if (syllable.vowelComponent() == BPMF::ENG && !syllable.hasConsonant() && if (syllable.vowelComponent() == BPMF::ENG && !syllable.hasConsonant() && !syllable.hasMiddleVowel())
!syllable.hasMiddleVowel()) { {
syllable += BPMF(BPMF::ERR); syllable += BPMF(BPMF::ERR);
} else if (syllable.consonantComponent() == BPMF::G && }
(syllable.middleVowelComponent() == BPMF::I || else if (syllable.consonantComponent() == BPMF::G &&
syllable.middleVowelComponent() == BPMF::UE)) { (syllable.middleVowelComponent() == BPMF::I || syllable.middleVowelComponent() == BPMF::UE))
{
syllable += BPMF(BPMF::J); syllable += BPMF(BPMF::J);
} }
} }
return syllable; return syllable;
} }
protected: protected:
bool endAheadOrAheadHasToneMarkKey(std::string::const_iterator ahead, bool endAheadOrAheadHasToneMarkKey(std::string::const_iterator ahead, std::string::const_iterator end) const
std::string::const_iterator end) const { {
if (ahead == end) return true; if (ahead == end)
return true;
char tone1 = componentToKey(BPMF::Tone1); char tone1 = componentToKey(BPMF::Tone1);
char tone2 = componentToKey(BPMF::Tone2); char tone2 = componentToKey(BPMF::Tone2);
char tone3 = componentToKey(BPMF::Tone3); char tone3 = componentToKey(BPMF::Tone3);
char tone4 = componentToKey(BPMF::Tone4); char tone4 = componentToKey(BPMF::Tone4);
char tone5 = componentToKey(BPMF::Tone5); char tone5 = componentToKey(BPMF::Tone5);
if (tone1) if (tone1)
if (*ahead == tone1) return true; if (*ahead == tone1)
return true;
if (*ahead == tone2 || *ahead == tone3 || *ahead == tone4 ||
*ahead == tone5) if (*ahead == tone2 || *ahead == tone3 || *ahead == tone4 || *ahead == tone5)
return true; return true;
return false; return false;
} }
bool sequenceContainsIorUE(std::string::const_iterator start, bool sequenceContainsIorUE(std::string::const_iterator start, std::string::const_iterator end) const
std::string::const_iterator end) const { {
char iChar = componentToKey(BPMF::I); char iChar = componentToKey(BPMF::I);
char ueChar = componentToKey(BPMF::UE); char ueChar = componentToKey(BPMF::UE);
for (; start != end; ++start) for (; start != end; ++start)
if (*start == iChar || *start == ueChar) return true; if (*start == iChar || *start == ueChar)
return true;
return false; return false;
} }
std::string m_name; std::string m_name;
BopomofoKeyToComponentMap m_keyToComponent; BopomofoKeyToComponentMap m_keyToComponent;
BopomofoComponentToKeyMap m_componentToKey; BopomofoComponentToKeyMap m_componentToKey;
}; };
class BopomofoReadingBuffer { class BopomofoReadingBuffer
public: {
explicit BopomofoReadingBuffer(const BopomofoKeyboardLayout* layout) public:
: layout_(layout), pinyin_mode_(false) { explicit BopomofoReadingBuffer(const BopomofoKeyboardLayout *layout) : layout_(layout), pinyin_mode_(false)
if (layout == BopomofoKeyboardLayout::HanyuPinyinLayout()) { {
if (layout == BopomofoKeyboardLayout::HanyuPinyinLayout())
{
pinyin_mode_ = true; pinyin_mode_ = true;
pinyin_sequence_ = ""; pinyin_sequence_ = "";
} }
} }
void setKeyboardLayout(const BopomofoKeyboardLayout* layout) { void setKeyboardLayout(const BopomofoKeyboardLayout *layout)
{
layout_ = layout; layout_ = layout;
if (layout == BopomofoKeyboardLayout::HanyuPinyinLayout()) { if (layout == BopomofoKeyboardLayout::HanyuPinyinLayout())
{
pinyin_mode_ = true; pinyin_mode_ = true;
pinyin_sequence_ = ""; pinyin_sequence_ = "";
} }
} }
bool isValidKey(char k) const { bool isValidKey(char k) const
if (!pinyin_mode_) { {
if (!pinyin_mode_)
{
return layout_ ? (layout_->keyToComponents(k)).size() > 0 : false; return layout_ ? (layout_->keyToComponents(k)).size() > 0 : false;
} }
char lk = tolower(k); char lk = tolower(k);
if (lk >= 'a' && lk <= 'z') { if (lk >= 'a' && lk <= 'z')
{
// if a tone marker is already in place // if a tone marker is already in place
if (pinyin_sequence_.length()) { if (pinyin_sequence_.length())
{
char lastc = pinyin_sequence_[pinyin_sequence_.length() - 1]; char lastc = pinyin_sequence_[pinyin_sequence_.length() - 1];
if (lastc >= '2' && lastc <= '5') { if (lastc >= '2' && lastc <= '5')
{
return false; return false;
} }
return true; return true;
} }
return true; return true;
} }
if (pinyin_sequence_.length() && (lk >= '2' && lk <= '5')) { if (pinyin_sequence_.length() && (lk >= '2' && lk <= '5'))
{
return true; return true;
} }
return false; return false;
} }
bool combineKey(char k) { bool combineKey(char k)
if (!isValidKey(k)) return false; {
if (!isValidKey(k))
if (pinyin_mode_) { return false;
if (pinyin_mode_)
{
pinyin_sequence_ += std::string(1, tolower(k)); pinyin_sequence_ += std::string(1, tolower(k));
syllable_ = BPMF::FromHanyuPinyin(pinyin_sequence_); syllable_ = BPMF::FromHanyuPinyin(pinyin_sequence_);
return true; return true;
} }
std::string sequence = std::string sequence = layout_->keySequenceFromSyllable(syllable_) + std::string(1, k);
layout_->keySequenceFromSyllable(syllable_) + std::string(1, k);
syllable_ = layout_->syllableFromKeySequence(sequence); syllable_ = layout_->syllableFromKeySequence(sequence);
return true; return true;
} }
void clear() { void clear()
{
pinyin_sequence_.clear(); pinyin_sequence_.clear();
syllable_.clear(); syllable_.clear();
} }
void backspace() { void backspace()
if (!layout_) return; {
if (!layout_)
if (pinyin_mode_) { return;
if (pinyin_sequence_.length()) {
pinyin_sequence_ = if (pinyin_mode_)
pinyin_sequence_.substr(0, pinyin_sequence_.length() - 1); {
if (pinyin_sequence_.length())
{
pinyin_sequence_ = pinyin_sequence_.substr(0, pinyin_sequence_.length() - 1);
} }
syllable_ = BPMF::FromHanyuPinyin(pinyin_sequence_); syllable_ = BPMF::FromHanyuPinyin(pinyin_sequence_);
return; return;
} }
std::string sequence = layout_->keySequenceFromSyllable(syllable_); std::string sequence = layout_->keySequenceFromSyllable(syllable_);
if (sequence.length()) { if (sequence.length())
{
sequence = sequence.substr(0, sequence.length() - 1); sequence = sequence.substr(0, sequence.length() - 1);
syllable_ = layout_->syllableFromKeySequence(sequence); syllable_ = layout_->syllableFromKeySequence(sequence);
} }
} }
bool isEmpty() const { return syllable_.isEmpty(); } bool isEmpty() const
{
const std::string composedString() const { return syllable_.isEmpty();
if (pinyin_mode_) { }
const std::string composedString() const
{
if (pinyin_mode_)
{
return pinyin_sequence_; return pinyin_sequence_;
} }
return syllable_.composedString(); return syllable_.composedString();
} }
const BPMF syllable() const { return syllable_; } const BPMF syllable() const
{
const std::string standardLayoutQueryString() const { return syllable_;
}
const std::string standardLayoutQueryString() const
{
return BopomofoKeyboardLayout::StandardLayout()->keySequenceFromSyllable(syllable_); return BopomofoKeyboardLayout::StandardLayout()->keySequenceFromSyllable(syllable_);
} }
bool hasToneMarker() const { return syllable_.hasToneMarker(); } bool hasToneMarker() const
{
protected: return syllable_.hasToneMarker();
const BopomofoKeyboardLayout* layout_; }
protected:
const BopomofoKeyboardLayout *layout_;
BPMF syllable_; BPMF syllable_;
bool pinyin_mode_; bool pinyin_mode_;
std::string pinyin_sequence_; std::string pinyin_sequence_;
}; };
} // namespace Mandarin } // namespace Mandarin
#endif // MANDARIN_H_
#endif // MANDARIN_H_

View File

@ -1,20 +1,27 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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 Foundation
@ -25,28 +32,28 @@ import OpenCC
/// Since SwiftyOpenCC only provide Swift classes, we create an NSObject subclass /// 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. /// in Swift in order to bridge the Swift classes into our Objective-C++ project.
public class OpenCCBridge: NSObject { public class OpenCCBridge: NSObject {
private static let shared = OpenCCBridge() private static let shared = OpenCCBridge()
private var simplify: ChineseConverter? private var simplify: ChineseConverter?
private var traditionalize: ChineseConverter? private var traditionalize: ChineseConverter?
private override init() {
try? simplify = ChineseConverter(options: .simplify)
try? traditionalize = ChineseConverter(options: [.traditionalize, .twStandard])
super.init()
}
/// CrossConvert. private override init() {
/// try? simplify = ChineseConverter(options: .simplify)
/// - Parameter string: Text in Original Script. try? traditionalize = ChineseConverter(options: [.traditionalize, .twStandard])
/// - Returns: Text converted to Different Script. super.init()
@objc public static func crossConvert(_ string: String) -> String? { }
switch ctlInputMethod.currentKeyHandler.inputMode {
case InputMode.imeModeCHS: /// CrossConvert.
return shared.traditionalize?.convert(string) ///
case InputMode.imeModeCHT: /// - Parameter string: Text in Original Script.
return shared.simplify?.convert(string) /// - Returns: Text converted to Different Script.
default: @objc public static func crossConvert(_ string: String) -> String? {
return string switch ctlInputMethod.currentKeyHandler.inputMode {
} case InputMode.imeModeCHS:
} return shared.traditionalize?.convert(string)
case InputMode.imeModeCHT:
return shared.simplify?.convert(string)
default:
return string
}
}
} }

@ -1 +1 @@
Subproject commit bc331e0dd76e4888ac2ef1d945b191994b86fbf4 Subproject commit 2c734867fe31b592c69a5b8f30008c4902800233

View File

@ -1,20 +1,27 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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.
*/ */
// //

View File

@ -1,20 +1,27 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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.
*/ */
// //

View File

@ -1,20 +1,27 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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 Cocoa
@ -29,287 +36,346 @@ private let kNextCheckInterval: TimeInterval = 86400.0
private let kTimeoutInterval: TimeInterval = 60.0 private let kTimeoutInterval: TimeInterval = 60.0
struct VersionUpdateReport { struct VersionUpdateReport {
var siteUrl: URL? var siteUrl: URL?
var currentShortVersion: String = "" var currentShortVersion: String = ""
var currentVersion: String = "" var currentVersion: String = ""
var remoteShortVersion: String = "" var remoteShortVersion: String = ""
var remoteVersion: String = "" var remoteVersion: String = ""
var versionDescription: String = "" var versionDescription: String = ""
} }
enum VersionUpdateApiResult { enum VersionUpdateApiResult {
case shouldUpdate(report: VersionUpdateReport) case shouldUpdate(report: VersionUpdateReport)
case noNeedToUpdate case noNeedToUpdate
case ignored case ignored
} }
enum VersionUpdateApiError: Error, LocalizedError { enum VersionUpdateApiError: Error, LocalizedError {
case connectionError(message: String) case connectionError(message: String)
var errorDescription: String? { var errorDescription: String? {
switch self { switch self {
case .connectionError(let message): 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) return String(
} format: NSLocalizedString(
} "There may be no internet connection or the server failed to respond.\n\nError message: %@",
comment: ""), message)
}
}
} }
struct VersionUpdateApi { struct VersionUpdateApi {
static func check(forced: Bool, callback: @escaping (Result<VersionUpdateApiResult, Error>) -> ()) -> URLSessionTask? { static func check(
guard let infoDict = Bundle.main.infoDictionary, forced: Bool, callback: @escaping (Result<VersionUpdateApiResult, Error>) -> Void
let updateInfoURLString = infoDict[kUpdateInfoEndpointKey] as? String, ) -> URLSessionTask? {
let updateInfoURL = URL(string: updateInfoURLString) else { guard let infoDict = Bundle.main.infoDictionary,
return nil let updateInfoURLString = infoDict[kUpdateInfoEndpointKey] as? String,
} let updateInfoURL = URL(string: updateInfoURLString)
else {
return nil
}
let request = URLRequest(url: updateInfoURL, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: kTimeoutInterval) let request = URLRequest(
let task = URLSession.shared.dataTask(with: request) { data, response, error in url: updateInfoURL, cachePolicy: .reloadIgnoringLocalCacheData,
if let error = error { timeoutInterval: kTimeoutInterval)
DispatchQueue.main.async { let task = URLSession.shared.dataTask(with: request) { data, response, error in
forced ? if let error = error {
callback(.failure(VersionUpdateApiError.connectionError(message: error.localizedDescription))) : DispatchQueue.main.async {
callback(.success(.ignored)) forced
} ? callback(
return .failure(
} VersionUpdateApiError.connectionError(
message: error.localizedDescription)))
: callback(.success(.ignored))
}
return
}
do { do {
guard let plist = try PropertyListSerialization.propertyList(from: data ?? Data(), options: [], format: nil) as? [AnyHashable: Any], guard
let remoteVersion = plist[kCFBundleVersionKey] as? String, let plist = try PropertyListSerialization.propertyList(
let infoDict = Bundle.main.infoDictionary from: data ?? Data(), options: [], format: nil) as? [AnyHashable: Any],
else { let remoteVersion = plist[kCFBundleVersionKey] as? String,
DispatchQueue.main.async { let infoDict = Bundle.main.infoDictionary
forced ? callback(.success(.noNeedToUpdate)) : callback(.success(.ignored)) else {
} DispatchQueue.main.async {
return forced
} ? callback(.success(.noNeedToUpdate))
: callback(.success(.ignored))
}
return
}
// TODO: Validate info (e.g. bundle identifier) // TODO: Validate info (e.g. bundle identifier)
// TODO: Use HTML to display change log, need a new key like UpdateInfoChangeLogURL for this // TODO: Use HTML to display change log, need a new key like UpdateInfoChangeLogURL for this
let currentVersion = infoDict[kCFBundleVersionKey as String] as? String ?? "" let currentVersion = infoDict[kCFBundleVersionKey as String] as? String ?? ""
let result = currentVersion.compare(remoteVersion, options: .numeric, range: nil, locale: nil) let result = currentVersion.compare(
remoteVersion, options: .numeric, range: nil, locale: nil)
if result != .orderedAscending { if result != .orderedAscending {
DispatchQueue.main.async { DispatchQueue.main.async {
forced ? callback(.success(.noNeedToUpdate)) : callback(.success(.ignored)) forced
} ? callback(.success(.noNeedToUpdate))
IME.prtDebugIntel("vChewingDebug: Update // Order is not Ascending, assuming that there's no new version available.") : callback(.success(.ignored))
return }
} IME.prtDebugIntel(
IME.prtDebugIntel("vChewingDebug: Update // New version detected, proceeding to the next phase.") "vChewingDebug: Update // Order is not Ascending, assuming that there's no new version available."
guard let siteInfoURLString = plist[kUpdateInfoSiteKey] as? String, )
let siteInfoURL = URL(string: siteInfoURLString) return
else { }
DispatchQueue.main.async { IME.prtDebugIntel(
forced ? callback(.success(.noNeedToUpdate)) : callback(.success(.ignored)) "vChewingDebug: Update // New version detected, proceeding to the next phase.")
} guard let siteInfoURLString = plist[kUpdateInfoSiteKey] as? String,
IME.prtDebugIntel("vChewingDebug: Update // Failed from retrieving / parsing URL intel.") let siteInfoURL = URL(string: siteInfoURLString)
return else {
} DispatchQueue.main.async {
IME.prtDebugIntel("vChewingDebug: Update // URL intel retrieved, proceeding to the next phase.") forced
var report = VersionUpdateReport(siteUrl: siteInfoURL) ? callback(.success(.noNeedToUpdate))
var versionDescription = "" : callback(.success(.ignored))
let versionDescriptions = plist[kVersionDescription] as? [AnyHashable: Any] }
if let versionDescriptions = versionDescriptions { IME.prtDebugIntel(
var locale = "en" "vChewingDebug: Update // Failed from retrieving / parsing URL intel.")
let supportedLocales = ["en", "zh-Hant", "zh-Hans", "ja"] return
let preferredTags = Bundle.preferredLocalizations(from: supportedLocales) }
if let first = preferredTags.first { IME.prtDebugIntel(
locale = first "vChewingDebug: Update // URL intel retrieved, proceeding to the next phase.")
} var report = VersionUpdateReport(siteUrl: siteInfoURL)
versionDescription = versionDescriptions[locale] as? String ?? versionDescriptions["en"] as? String ?? "" var versionDescription = ""
if !versionDescription.isEmpty { let versionDescriptions = plist[kVersionDescription] as? [AnyHashable: Any]
versionDescription = "\n\n" + versionDescription if let versionDescriptions = versionDescriptions {
} var locale = "en"
} let supportedLocales = ["en", "zh-Hant", "zh-Hans", "ja"]
report.currentShortVersion = infoDict["CFBundleShortVersionString"] as? String ?? "" let preferredTags = Bundle.preferredLocalizations(from: supportedLocales)
report.currentVersion = currentVersion if let first = preferredTags.first {
report.remoteShortVersion = plist["CFBundleShortVersionString"] as? String ?? "" locale = first
report.remoteVersion = remoteVersion }
report.versionDescription = versionDescription versionDescription =
DispatchQueue.main.async { versionDescriptions[locale] as? String ?? versionDescriptions["en"]
callback(.success(.shouldUpdate(report: report))) as? String ?? ""
} if !versionDescription.isEmpty {
IME.prtDebugIntel("vChewingDebug: Update // Callbck Complete.") versionDescription = "\n\n" + versionDescription
} catch { }
DispatchQueue.main.async { }
forced ? callback(.success(.noNeedToUpdate)) : callback(.success(.ignored)) report.currentShortVersion = infoDict["CFBundleShortVersionString"] as? String ?? ""
} report.currentVersion = currentVersion
} report.remoteShortVersion = plist["CFBundleShortVersionString"] as? String ?? ""
} report.remoteVersion = remoteVersion
task.resume() report.versionDescription = versionDescription
return task 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) @objc(AppDelegate)
class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelegate, FSEventStreamHelperDelegate { class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelegate,
func helper(_ helper: FSEventStreamHelper, didReceive events: [FSEventStreamHelper.Event]) { FSEventStreamHelperDelegate
// 100ms 使使 {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) { func helper(_ helper: FSEventStreamHelper, didReceive events: [FSEventStreamHelper.Event]) {
if mgrPrefs.shouldAutoReloadUserDataFiles { // 100ms 使使
IME.initLangModels(userOnly: true) 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? @IBOutlet weak var window: NSWindow?
private var ctlPrefWindowInstance: ctlPrefWindow? private var ctlPrefWindowInstance: ctlPrefWindow?
private var ctlAboutWindowInstance: ctlAboutWindow? // New About Window private var ctlAboutWindowInstance: ctlAboutWindow? // New About Window
private var checkTask: URLSessionTask? private var checkTask: URLSessionTask?
private var updateNextStepURL: URL? private var updateNextStepURL: URL?
private var fsStreamHelper = FSEventStreamHelper(path: mgrLangModel.dataFolderPath(isDefaultFolder: false), queue: DispatchQueue(label: "vChewing User Phrases")) private var fsStreamHelper = FSEventStreamHelper(
private var currentAlertType: String = "" path: mgrLangModel.dataFolderPath(isDefaultFolder: false),
queue: DispatchQueue(label: "vChewing User Phrases"))
private var currentAlertType: String = ""
// dealloc // dealloc
deinit { deinit {
ctlPrefWindowInstance = nil ctlPrefWindowInstance = nil
ctlAboutWindowInstance = nil ctlAboutWindowInstance = nil
checkTask = nil checkTask = nil
updateNextStepURL = nil updateNextStepURL = nil
fsStreamHelper.stop() fsStreamHelper.stop()
fsStreamHelper.delegate = nil fsStreamHelper.delegate = nil
} }
func applicationDidFinishLaunching(_ notification: Notification) { func applicationDidFinishLaunching(_ notification: Notification) {
IME.initLangModels(userOnly: false) IME.initLangModels(userOnly: false)
fsStreamHelper.delegate = self fsStreamHelper.delegate = self
_ = fsStreamHelper.start() _ = fsStreamHelper.start()
mgrPrefs.setMissingDefaults() mgrPrefs.setMissingDefaults()
// 使
if (UserDefaults.standard.object(forKey: kCheckUpdateAutomatically) != nil) == true {
checkForUpdate()
}
}
@objc func showPreferences() { // 使
if (ctlPrefWindowInstance == nil) { if (UserDefaults.standard.object(forKey: kCheckUpdateAutomatically) != nil) == true {
ctlPrefWindowInstance = ctlPrefWindow.init(windowNibName: "frmPrefWindow") checkForUpdate()
} }
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)
}
@objc(checkForUpdate) @objc func showPreferences() {
func checkForUpdate() { if ctlPrefWindowInstance == nil {
checkForUpdate(forced: false) ctlPrefWindowInstance = ctlPrefWindow.init(windowNibName: "frmPrefWindow")
} }
ctlPrefWindowInstance?.window?.center()
ctlPrefWindowInstance?.window?.orderFrontRegardless() //
ctlPrefWindowInstance?.window?.level = .statusBar
ctlPrefWindowInstance?.window?.titlebarAppearsTransparent = true
NSApp.setActivationPolicy(.accessory)
}
@objc(checkForUpdateForced:) // New About Window
func checkForUpdate(forced: Bool) { @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 { @objc(checkForUpdate)
// busy func checkForUpdate() {
return checkForUpdate(forced: false)
} }
// time for update? @objc(checkForUpdateForced:)
if !forced { func checkForUpdate(forced: Bool) {
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
}
}
let nextUpdateDate = Date(timeInterval: kNextCheckInterval, since: Date()) if checkTask != nil {
UserDefaults.standard.set(nextUpdateDate, forKey: kNextUpdateCheckDateKey) // busy
return
}
checkTask = VersionUpdateApi.check(forced: forced) { [self] result in // time for update?
defer { if !forced {
self.checkTask = nil if UserDefaults.standard.bool(forKey: kCheckUpdateAutomatically) == false {
} return
switch result { }
case .success(let apiResult): let now = Date()
switch apiResult { let date = UserDefaults.standard.object(forKey: kNextUpdateCheckDateKey) as? Date ?? now
case .shouldUpdate(let report): if now.compare(date) == .orderedAscending {
self.updateNextStepURL = report.siteUrl return
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 selfUninstall() { let nextUpdateDate = Date(timeInterval: kNextCheckInterval, since: Date())
self.currentAlertType = "Uninstall" UserDefaults.standard.set(nextUpdateDate, forKey: kNextUpdateCheckDateKey)
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)
}
func ctlNonModalAlertWindowDidConfirm(_ controller: ctlNonModalAlertWindow) { checkTask = VersionUpdateApi.check(forced: forced) { [self] result in
switch self.currentAlertType { defer {
case "Uninstall": self.checkTask = nil
NSWorkspace.shared.openFile(mgrLangModel.dataFolderPath(isDefaultFolder: true), withApplication: "Finder") }
IME.uninstall(isSudo: false, selfKill: true) switch result {
case "Update": case .success(let apiResult):
if let updateNextStepURL = self.updateNextStepURL { switch apiResult {
NSWorkspace.shared.open(updateNextStepURL) case .shouldUpdate(let report):
} self.updateNextStepURL = report.siteUrl
self.updateNextStepURL = nil let content = String(
default: format: NSLocalizedString(
break "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) { func selfUninstall() {
switch self.currentAlertType { self.currentAlertType = "Uninstall"
case "Update": let content = String(
self.updateNextStepURL = nil format: NSLocalizedString(
default: "This will remove vChewing Input Method from this user account, requiring your confirmation.",
break 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 func ctlNonModalAlertWindowDidConfirm(_ controller: ctlNonModalAlertWindow) {
@IBAction func about(_ sender: Any) { switch self.currentAlertType {
(NSApp.delegate as? AppDelegate)?.showAbout() case "Uninstall":
NSApplication.shared.activate(ignoringOtherApps: true) 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)
}
} }

View File

@ -1,329 +1,339 @@
// Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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 Cocoa
@objc class AppleKeyboardConverter: NSObject { @objc class AppleKeyboardConverter: NSObject {
@objc class func isDynamicBaseKeyboardLayoutEnabled() -> Bool { @objc class func isDynamicBaseKeyboardLayoutEnabled() -> Bool {
switch mgrPrefs.basisKeyboardLayout { switch mgrPrefs.basisKeyboardLayout {
case "com.apple.keylayout.ZhuyinBopomofo": case "com.apple.keylayout.ZhuyinBopomofo":
return true return true
case "com.apple.keylayout.ZhuyinEten": case "com.apple.keylayout.ZhuyinEten":
return true return true
case "org.atelierInmu.vChewing.keyLayouts.vchewingdachen": case "org.atelierInmu.vChewing.keyLayouts.vchewingdachen":
return true return true
case "org.atelierInmu.vChewing.keyLayouts.vchewingmitac": case "org.atelierInmu.vChewing.keyLayouts.vchewingmitac":
return true return true
case "org.atelierInmu.vChewing.keyLayouts.vchewingibm": case "org.atelierInmu.vChewing.keyLayouts.vchewingibm":
return true return true
case "org.atelierInmu.vChewing.keyLayouts.vchewingseigyou": case "org.atelierInmu.vChewing.keyLayouts.vchewingseigyou":
return true return true
case "org.atelierInmu.vChewing.keyLayouts.vchewingeten": case "org.atelierInmu.vChewing.keyLayouts.vchewingeten":
return true return true
case "org.unknown.keylayout.vChewingDachen": case "org.unknown.keylayout.vChewingDachen":
return true return true
case "org.unknown.keylayout.vChewingFakeSeigyou": case "org.unknown.keylayout.vChewingFakeSeigyou":
return true return true
case "org.unknown.keylayout.vChewingETen": case "org.unknown.keylayout.vChewingETen":
return true return true
case "org.unknown.keylayout.vChewingIBM": case "org.unknown.keylayout.vChewingIBM":
return true return true
case "org.unknown.keylayout.vChewingMiTAC": case "org.unknown.keylayout.vChewingMiTAC":
return true return true
default: default:
return false return false
} }
} }
// Apple // Apple
@objc class func cnvApple2ABC(_ charCode: UniChar) -> UniChar { @objc class func cnvApple2ABC(_ charCode: UniChar) -> UniChar {
var charCode = charCode var charCode = charCode
// OVMandarin OVMandarin // OVMandarin OVMandarin
if self.isDynamicBaseKeyboardLayoutEnabled() { if self.isDynamicBaseKeyboardLayoutEnabled() {
// Apple // Apple
switch mgrPrefs.basisKeyboardLayout { switch mgrPrefs.basisKeyboardLayout {
case "com.apple.keylayout.ZhuyinBopomofo": do { case "com.apple.keylayout.ZhuyinBopomofo":
if (charCode == 97) {charCode = UniChar(65)} do {
if (charCode == 98) {charCode = UniChar(66)} if charCode == 97 { charCode = UniChar(65) }
if (charCode == 99) {charCode = UniChar(67)} if charCode == 98 { charCode = UniChar(66) }
if (charCode == 100) {charCode = UniChar(68)} if charCode == 99 { charCode = UniChar(67) }
if (charCode == 101) {charCode = UniChar(69)} if charCode == 100 { charCode = UniChar(68) }
if (charCode == 102) {charCode = UniChar(70)} if charCode == 101 { charCode = UniChar(69) }
if (charCode == 103) {charCode = UniChar(71)} if charCode == 102 { charCode = UniChar(70) }
if (charCode == 104) {charCode = UniChar(72)} if charCode == 103 { charCode = UniChar(71) }
if (charCode == 105) {charCode = UniChar(73)} if charCode == 104 { charCode = UniChar(72) }
if (charCode == 106) {charCode = UniChar(74)} if charCode == 105 { charCode = UniChar(73) }
if (charCode == 107) {charCode = UniChar(75)} if charCode == 106 { charCode = UniChar(74) }
if (charCode == 108) {charCode = UniChar(76)} if charCode == 107 { charCode = UniChar(75) }
if (charCode == 109) {charCode = UniChar(77)} if charCode == 108 { charCode = UniChar(76) }
if (charCode == 110) {charCode = UniChar(78)} if charCode == 109 { charCode = UniChar(77) }
if (charCode == 111) {charCode = UniChar(79)} if charCode == 110 { charCode = UniChar(78) }
if (charCode == 112) {charCode = UniChar(80)} if charCode == 111 { charCode = UniChar(79) }
if (charCode == 113) {charCode = UniChar(81)} if charCode == 112 { charCode = UniChar(80) }
if (charCode == 114) {charCode = UniChar(82)} if charCode == 113 { charCode = UniChar(81) }
if (charCode == 115) {charCode = UniChar(83)} if charCode == 114 { charCode = UniChar(82) }
if (charCode == 116) {charCode = UniChar(84)} if charCode == 115 { charCode = UniChar(83) }
if (charCode == 117) {charCode = UniChar(85)} if charCode == 116 { charCode = UniChar(84) }
if (charCode == 118) {charCode = UniChar(86)} if charCode == 117 { charCode = UniChar(85) }
if (charCode == 119) {charCode = UniChar(87)} if charCode == 118 { charCode = UniChar(86) }
if (charCode == 120) {charCode = UniChar(88)} if charCode == 119 { charCode = UniChar(87) }
if (charCode == 121) {charCode = UniChar(89)} if charCode == 120 { charCode = UniChar(88) }
if (charCode == 122) {charCode = UniChar(90)} if charCode == 121 { charCode = UniChar(89) }
} if charCode == 122 { charCode = UniChar(90) }
case "com.apple.keylayout.ZhuyinEten": do { }
if (charCode == 65345) {charCode = UniChar(65)} case "com.apple.keylayout.ZhuyinEten":
if (charCode == 65346) {charCode = UniChar(66)} do {
if (charCode == 65347) {charCode = UniChar(67)} if charCode == 65345 { charCode = UniChar(65) }
if (charCode == 65348) {charCode = UniChar(68)} if charCode == 65346 { charCode = UniChar(66) }
if (charCode == 65349) {charCode = UniChar(69)} if charCode == 65347 { charCode = UniChar(67) }
if (charCode == 65350) {charCode = UniChar(70)} if charCode == 65348 { charCode = UniChar(68) }
if (charCode == 65351) {charCode = UniChar(71)} if charCode == 65349 { charCode = UniChar(69) }
if (charCode == 65352) {charCode = UniChar(72)} if charCode == 65350 { charCode = UniChar(70) }
if (charCode == 65353) {charCode = UniChar(73)} if charCode == 65351 { charCode = UniChar(71) }
if (charCode == 65354) {charCode = UniChar(74)} if charCode == 65352 { charCode = UniChar(72) }
if (charCode == 65355) {charCode = UniChar(75)} if charCode == 65353 { charCode = UniChar(73) }
if (charCode == 65356) {charCode = UniChar(76)} if charCode == 65354 { charCode = UniChar(74) }
if (charCode == 65357) {charCode = UniChar(77)} if charCode == 65355 { charCode = UniChar(75) }
if (charCode == 65358) {charCode = UniChar(78)} if charCode == 65356 { charCode = UniChar(76) }
if (charCode == 65359) {charCode = UniChar(79)} if charCode == 65357 { charCode = UniChar(77) }
if (charCode == 65360) {charCode = UniChar(80)} if charCode == 65358 { charCode = UniChar(78) }
if (charCode == 65361) {charCode = UniChar(81)} if charCode == 65359 { charCode = UniChar(79) }
if (charCode == 65362) {charCode = UniChar(82)} if charCode == 65360 { charCode = UniChar(80) }
if (charCode == 65363) {charCode = UniChar(83)} if charCode == 65361 { charCode = UniChar(81) }
if (charCode == 65364) {charCode = UniChar(84)} if charCode == 65362 { charCode = UniChar(82) }
if (charCode == 65365) {charCode = UniChar(85)} if charCode == 65363 { charCode = UniChar(83) }
if (charCode == 65366) {charCode = UniChar(86)} if charCode == 65364 { charCode = UniChar(84) }
if (charCode == 65367) {charCode = UniChar(87)} if charCode == 65365 { charCode = UniChar(85) }
if (charCode == 65368) {charCode = UniChar(88)} if charCode == 65366 { charCode = UniChar(86) }
if (charCode == 65369) {charCode = UniChar(89)} if charCode == 65367 { charCode = UniChar(87) }
if (charCode == 65370) {charCode = UniChar(90)} if charCode == 65368 { charCode = UniChar(88) }
} if charCode == 65369 { charCode = UniChar(89) }
default: break 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 == 12573 { charCode = UniChar(44) }
if (charCode == 12581) {charCode = UniChar(47)} if charCode == 12582 { charCode = UniChar(45) }
if (charCode == 12578) {charCode = UniChar(48)} if charCode == 12577 { charCode = UniChar(46) }
if (charCode == 12549) {charCode = UniChar(49)} if charCode == 12581 { charCode = UniChar(47) }
if (charCode == 12553) {charCode = UniChar(50)} if charCode == 12578 { charCode = UniChar(48) }
if (charCode == 711) {charCode = UniChar(51)} if charCode == 12549 { charCode = UniChar(49) }
if (charCode == 715) {charCode = UniChar(52)} if charCode == 12553 { charCode = UniChar(50) }
if (charCode == 12563) {charCode = UniChar(53)} if charCode == 711 { charCode = UniChar(51) }
if (charCode == 714) {charCode = UniChar(54)} if charCode == 715 { charCode = UniChar(52) }
if (charCode == 729) {charCode = UniChar(55)} if charCode == 12563 { charCode = UniChar(53) }
if (charCode == 12570) {charCode = UniChar(56)} if charCode == 714 { charCode = UniChar(54) }
if (charCode == 12574) {charCode = UniChar(57)} if charCode == 729 { charCode = UniChar(55) }
if (charCode == 12580) {charCode = UniChar(59)} if charCode == 12570 { charCode = UniChar(56) }
if (charCode == 12551) {charCode = UniChar(97)} if charCode == 12574 { charCode = UniChar(57) }
if (charCode == 12566) {charCode = UniChar(98)} if charCode == 12580 { charCode = UniChar(59) }
if (charCode == 12559) {charCode = UniChar(99)} if charCode == 12551 { charCode = UniChar(97) }
if (charCode == 12558) {charCode = UniChar(100)} if charCode == 12566 { charCode = UniChar(98) }
if (charCode == 12557) {charCode = UniChar(101)} if charCode == 12559 { charCode = UniChar(99) }
if (charCode == 12561) {charCode = UniChar(102)} if charCode == 12558 { charCode = UniChar(100) }
if (charCode == 12565) {charCode = UniChar(103)} if charCode == 12557 { charCode = UniChar(101) }
if (charCode == 12568) {charCode = UniChar(104)} if charCode == 12561 { charCode = UniChar(102) }
if (charCode == 12571) {charCode = UniChar(105)} if charCode == 12565 { charCode = UniChar(103) }
if (charCode == 12584) {charCode = UniChar(106)} if charCode == 12568 { charCode = UniChar(104) }
if (charCode == 12572) {charCode = UniChar(107)} if charCode == 12571 { charCode = UniChar(105) }
if (charCode == 12576) {charCode = UniChar(108)} if charCode == 12584 { charCode = UniChar(106) }
if (charCode == 12585) {charCode = UniChar(109)} if charCode == 12572 { charCode = UniChar(107) }
if (charCode == 12569) {charCode = UniChar(110)} if charCode == 12576 { charCode = UniChar(108) }
if (charCode == 12575) {charCode = UniChar(111)} if charCode == 12585 { charCode = UniChar(109) }
if (charCode == 12579) {charCode = UniChar(112)} if charCode == 12569 { charCode = UniChar(110) }
if (charCode == 12550) {charCode = UniChar(113)} if charCode == 12575 { charCode = UniChar(111) }
if (charCode == 12560) {charCode = UniChar(114)} if charCode == 12579 { charCode = UniChar(112) }
if (charCode == 12555) {charCode = UniChar(115)} if charCode == 12550 { charCode = UniChar(113) }
if (charCode == 12564) {charCode = UniChar(116)} if charCode == 12560 { charCode = UniChar(114) }
if (charCode == 12583) {charCode = UniChar(117)} if charCode == 12555 { charCode = UniChar(115) }
if (charCode == 12562) {charCode = UniChar(118)} if charCode == 12564 { charCode = UniChar(116) }
if (charCode == 12554) {charCode = UniChar(119)} if charCode == 12583 { charCode = UniChar(117) }
if (charCode == 12556) {charCode = UniChar(120)} if charCode == 12562 { charCode = UniChar(118) }
if (charCode == 12567) {charCode = UniChar(121)} if charCode == 12554 { charCode = UniChar(119) }
if (charCode == 12552) {charCode = UniChar(122)} if charCode == 12556 { charCode = UniChar(120) }
// if charCode == 12567 { charCode = UniChar(121) }
if (charCode == 12289) {charCode = UniChar(92)} if charCode == 12552 { charCode = UniChar(122) }
if (charCode == 12300) {charCode = UniChar(91)} //
if (charCode == 12301) {charCode = UniChar(93)} if charCode == 12289 { charCode = UniChar(92) }
if (charCode == 12302) {charCode = UniChar(123)} if charCode == 12300 { charCode = UniChar(91) }
if (charCode == 12303) {charCode = UniChar(125)} if charCode == 12301 { charCode = UniChar(93) }
if (charCode == 65292) {charCode = UniChar(60)} if charCode == 12302 { charCode = UniChar(123) }
if (charCode == 12290) {charCode = UniChar(62)} if charCode == 12303 { charCode = UniChar(125) }
// SHIFT if charCode == 65292 { charCode = UniChar(60) }
if (charCode == 65281) {charCode = UniChar(33)} if charCode == 12290 { charCode = UniChar(62) }
if (charCode == 65312) {charCode = UniChar(64)} // SHIFT
if (charCode == 65283) {charCode = UniChar(35)} if charCode == 65281 { charCode = UniChar(33) }
if (charCode == 65284) {charCode = UniChar(36)} if charCode == 65312 { charCode = UniChar(64) }
if (charCode == 65285) {charCode = UniChar(37)} if charCode == 65283 { charCode = UniChar(35) }
if (charCode == 65087) {charCode = UniChar(94)} if charCode == 65284 { charCode = UniChar(36) }
if (charCode == 65286) {charCode = UniChar(38)} if charCode == 65285 { charCode = UniChar(37) }
if (charCode == 65290) {charCode = UniChar(42)} if charCode == 65087 { charCode = UniChar(94) }
if (charCode == 65288) {charCode = UniChar(40)} if charCode == 65286 { charCode = UniChar(38) }
if (charCode == 65289) {charCode = UniChar(41)} if charCode == 65290 { charCode = UniChar(42) }
// Alt if charCode == 65288 { charCode = UniChar(40) }
if (charCode == 8212) {charCode = UniChar(45)} if charCode == 65289 { charCode = UniChar(41) }
// Apple // Alt
if mgrPrefs.basisKeyboardLayout == "com.apple.keylayout.ZhuyinEten" { if charCode == 8212 { charCode = UniChar(45) }
if (charCode == 65343) {charCode = UniChar(95)} // Apple
if (charCode == 65306) {charCode = UniChar(58)} if mgrPrefs.basisKeyboardLayout == "com.apple.keylayout.ZhuyinEten" {
if (charCode == 65311) {charCode = UniChar(63)} if charCode == 65343 { charCode = UniChar(95) }
if (charCode == 65291) {charCode = UniChar(43)} if charCode == 65306 { charCode = UniChar(58) }
if (charCode == 65372) {charCode = UniChar(124)} if charCode == 65311 { charCode = UniChar(63) }
} if charCode == 65291 { charCode = UniChar(43) }
} if charCode == 65372 { charCode = UniChar(124) }
return charCode }
} }
return charCode
}
@objc class func cnvStringApple2ABC(_ strProcessed: String) -> String { @objc class func cnvStringApple2ABC(_ strProcessed: String) -> String {
var strProcessed = strProcessed var strProcessed = strProcessed
if self.isDynamicBaseKeyboardLayoutEnabled() { if self.isDynamicBaseKeyboardLayoutEnabled() {
// Apple // Apple
switch mgrPrefs.basisKeyboardLayout { switch mgrPrefs.basisKeyboardLayout {
case "com.apple.keylayout.ZhuyinBopomofo": do { case "com.apple.keylayout.ZhuyinBopomofo":
if (strProcessed == "a") {strProcessed = "A"} do {
if (strProcessed == "b") {strProcessed = "B"} if strProcessed == "a" { strProcessed = "A" }
if (strProcessed == "c") {strProcessed = "C"} if strProcessed == "b" { strProcessed = "B" }
if (strProcessed == "d") {strProcessed = "D"} if strProcessed == "c" { strProcessed = "C" }
if (strProcessed == "e") {strProcessed = "E"} if strProcessed == "d" { strProcessed = "D" }
if (strProcessed == "f") {strProcessed = "F"} if strProcessed == "e" { strProcessed = "E" }
if (strProcessed == "g") {strProcessed = "G"} if strProcessed == "f" { strProcessed = "F" }
if (strProcessed == "h") {strProcessed = "H"} if strProcessed == "g" { strProcessed = "G" }
if (strProcessed == "i") {strProcessed = "I"} if strProcessed == "h" { strProcessed = "H" }
if (strProcessed == "j") {strProcessed = "J"} if strProcessed == "i" { strProcessed = "I" }
if (strProcessed == "k") {strProcessed = "K"} if strProcessed == "j" { strProcessed = "J" }
if (strProcessed == "l") {strProcessed = "L"} if strProcessed == "k" { strProcessed = "K" }
if (strProcessed == "m") {strProcessed = "M"} if strProcessed == "l" { strProcessed = "L" }
if (strProcessed == "n") {strProcessed = "N"} if strProcessed == "m" { strProcessed = "M" }
if (strProcessed == "o") {strProcessed = "O"} if strProcessed == "n" { strProcessed = "N" }
if (strProcessed == "p") {strProcessed = "P"} if strProcessed == "o" { strProcessed = "O" }
if (strProcessed == "q") {strProcessed = "Q"} if strProcessed == "p" { strProcessed = "P" }
if (strProcessed == "r") {strProcessed = "R"} if strProcessed == "q" { strProcessed = "Q" }
if (strProcessed == "s") {strProcessed = "S"} if strProcessed == "r" { strProcessed = "R" }
if (strProcessed == "t") {strProcessed = "T"} if strProcessed == "s" { strProcessed = "S" }
if (strProcessed == "u") {strProcessed = "U"} if strProcessed == "t" { strProcessed = "T" }
if (strProcessed == "v") {strProcessed = "V"} if strProcessed == "u" { strProcessed = "U" }
if (strProcessed == "w") {strProcessed = "W"} if strProcessed == "v" { strProcessed = "V" }
if (strProcessed == "x") {strProcessed = "X"} if strProcessed == "w" { strProcessed = "W" }
if (strProcessed == "y") {strProcessed = "Y"} if strProcessed == "x" { strProcessed = "X" }
if (strProcessed == "z") {strProcessed = "Z"} if strProcessed == "y" { strProcessed = "Y" }
} if strProcessed == "z" { strProcessed = "Z" }
case "com.apple.keylayout.ZhuyinEten": do { }
if (strProcessed == "") {strProcessed = "A"} case "com.apple.keylayout.ZhuyinEten":
if (strProcessed == "") {strProcessed = "B"} do {
if (strProcessed == "") {strProcessed = "C"} if strProcessed == "" { strProcessed = "A" }
if (strProcessed == "") {strProcessed = "D"} if strProcessed == "" { strProcessed = "B" }
if (strProcessed == "") {strProcessed = "E"} if strProcessed == "" { strProcessed = "C" }
if (strProcessed == "") {strProcessed = "F"} if strProcessed == "" { strProcessed = "D" }
if (strProcessed == "") {strProcessed = "G"} if strProcessed == "" { strProcessed = "E" }
if (strProcessed == "") {strProcessed = "H"} if strProcessed == "" { strProcessed = "F" }
if (strProcessed == "") {strProcessed = "I"} if strProcessed == "" { strProcessed = "G" }
if (strProcessed == "") {strProcessed = "J"} if strProcessed == "" { strProcessed = "H" }
if (strProcessed == "") {strProcessed = "K"} if strProcessed == "" { strProcessed = "I" }
if (strProcessed == "") {strProcessed = "L"} if strProcessed == "" { strProcessed = "J" }
if (strProcessed == "") {strProcessed = "M"} if strProcessed == "" { strProcessed = "K" }
if (strProcessed == "") {strProcessed = "N"} if strProcessed == "" { strProcessed = "L" }
if (strProcessed == "") {strProcessed = "O"} if strProcessed == "" { strProcessed = "M" }
if (strProcessed == "") {strProcessed = "P"} if strProcessed == "" { strProcessed = "N" }
if (strProcessed == "") {strProcessed = "Q"} if strProcessed == "" { strProcessed = "O" }
if (strProcessed == "") {strProcessed = "R"} if strProcessed == "" { strProcessed = "P" }
if (strProcessed == "") {strProcessed = "S"} if strProcessed == "" { strProcessed = "Q" }
if (strProcessed == "") {strProcessed = "T"} if strProcessed == "" { strProcessed = "R" }
if (strProcessed == "") {strProcessed = "U"} if strProcessed == "" { strProcessed = "S" }
if (strProcessed == "") {strProcessed = "V"} if strProcessed == "" { strProcessed = "T" }
if (strProcessed == "") {strProcessed = "W"} if strProcessed == "" { strProcessed = "U" }
if (strProcessed == "") {strProcessed = "X"} if strProcessed == "" { strProcessed = "V" }
if (strProcessed == "") {strProcessed = "Y"} if strProcessed == "" { strProcessed = "W" }
if (strProcessed == "") {strProcessed = "Z"} if strProcessed == "" { strProcessed = "X" }
} if strProcessed == "" { strProcessed = "Y" }
default: break if strProcessed == "" { strProcessed = "Z" }
} }
// default: break
if (strProcessed == "") {strProcessed = ","} }
if (strProcessed == "") {strProcessed = "-"} //
if (strProcessed == "") {strProcessed = "."} if strProcessed == "" { strProcessed = "," }
if (strProcessed == "") {strProcessed = "/"} if strProcessed == "" { strProcessed = "-" }
if (strProcessed == "") {strProcessed = "0"} if strProcessed == "" { strProcessed = "." }
if (strProcessed == "") {strProcessed = "1"} if strProcessed == "" { strProcessed = "/" }
if (strProcessed == "") {strProcessed = "2"} if strProcessed == "" { strProcessed = "0" }
if (strProcessed == "ˇ") {strProcessed = "3"} if strProcessed == "" { strProcessed = "1" }
if (strProcessed == "ˋ") {strProcessed = "4"} if strProcessed == "" { strProcessed = "2" }
if (strProcessed == "") {strProcessed = "5"} if strProcessed == "ˇ" { strProcessed = "3" }
if (strProcessed == "ˊ") {strProcessed = "6"} if strProcessed == "ˋ" { strProcessed = "4" }
if (strProcessed == "˙") {strProcessed = "7"} if strProcessed == "" { strProcessed = "5" }
if (strProcessed == "") {strProcessed = "8"} if strProcessed == "ˊ" { strProcessed = "6" }
if (strProcessed == "") {strProcessed = "9"} if strProcessed == "˙" { strProcessed = "7" }
if (strProcessed == "") {strProcessed = ";"} if strProcessed == "" { strProcessed = "8" }
if (strProcessed == "") {strProcessed = "a"} if strProcessed == "" { strProcessed = "9" }
if (strProcessed == "") {strProcessed = "b"} if strProcessed == "" { strProcessed = ";" }
if (strProcessed == "") {strProcessed = "c"} if strProcessed == "" { strProcessed = "a" }
if (strProcessed == "") {strProcessed = "d"} if strProcessed == "" { strProcessed = "b" }
if (strProcessed == "") {strProcessed = "e"} if strProcessed == "" { strProcessed = "c" }
if (strProcessed == "") {strProcessed = "f"} if strProcessed == "" { strProcessed = "d" }
if (strProcessed == "") {strProcessed = "g"} if strProcessed == "" { strProcessed = "e" }
if (strProcessed == "") {strProcessed = "h"} if strProcessed == "" { strProcessed = "f" }
if (strProcessed == "") {strProcessed = "i"} if strProcessed == "" { strProcessed = "g" }
if (strProcessed == "") {strProcessed = "j"} if strProcessed == "" { strProcessed = "h" }
if (strProcessed == "") {strProcessed = "k"} if strProcessed == "" { strProcessed = "i" }
if (strProcessed == "") {strProcessed = "l"} if strProcessed == "" { strProcessed = "j" }
if (strProcessed == "") {strProcessed = "m"} if strProcessed == "" { strProcessed = "k" }
if (strProcessed == "") {strProcessed = "n"} if strProcessed == "" { strProcessed = "l" }
if (strProcessed == "") {strProcessed = "o"} if strProcessed == "" { strProcessed = "m" }
if (strProcessed == "") {strProcessed = "p"} if strProcessed == "" { strProcessed = "n" }
if (strProcessed == "") {strProcessed = "q"} if strProcessed == "" { strProcessed = "o" }
if (strProcessed == "") {strProcessed = "r"} if strProcessed == "" { strProcessed = "p" }
if (strProcessed == "") {strProcessed = "s"} if strProcessed == "" { strProcessed = "q" }
if (strProcessed == "") {strProcessed = "t"} if strProcessed == "" { strProcessed = "r" }
if (strProcessed == "") {strProcessed = "u"} if strProcessed == "" { strProcessed = "s" }
if (strProcessed == "") {strProcessed = "v"} if strProcessed == "" { strProcessed = "t" }
if (strProcessed == "") {strProcessed = "w"} if strProcessed == "" { strProcessed = "u" }
if (strProcessed == "") {strProcessed = "x"} if strProcessed == "" { strProcessed = "v" }
if (strProcessed == "") {strProcessed = "y"} if strProcessed == "" { strProcessed = "w" }
if (strProcessed == "") {strProcessed = "z"} if strProcessed == "" { strProcessed = "x" }
// if strProcessed == "" { strProcessed = "y" }
if (strProcessed == "") {strProcessed = "\\"} 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 = "]" }
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 = "@"} // 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 = "%" }
if (strProcessed == "") {strProcessed = "*"} if strProcessed == "︿" { strProcessed = "^" }
if (strProcessed == "") {strProcessed = "("} if strProcessed == "" { strProcessed = "&" }
if (strProcessed == "") {strProcessed = ")"} if strProcessed == "" { strProcessed = "*" }
// Alt if strProcessed == "" { strProcessed = "(" }
if (strProcessed == "") {strProcessed = "-"} if strProcessed == "" { strProcessed = ")" }
// Apple // Alt
if mgrPrefs.basisKeyboardLayout == "com.apple.keylayout.ZhuyinEten" { if strProcessed == "" { strProcessed = "-" }
if (strProcessed == "_") {strProcessed = "_"} // Apple
if (strProcessed == "") {strProcessed = ":"} if mgrPrefs.basisKeyboardLayout == "com.apple.keylayout.ZhuyinEten" {
if (strProcessed == "") {strProcessed = "?"} if strProcessed == "_" { strProcessed = "_" }
if (strProcessed == "") {strProcessed = "+"} if strProcessed == "" { strProcessed = ":" }
if (strProcessed == "") {strProcessed = "|"} if strProcessed == "" { strProcessed = "?" }
} if strProcessed == "" { strProcessed = "+" }
} if strProcessed == "" { strProcessed = "|" }
return strProcessed }
} }
return strProcessed
}
} }

View File

@ -1,20 +1,27 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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 Cocoa
@ -52,359 +59,424 @@ import Cocoa
/// one among the candidates. /// one among the candidates.
class InputState: NSObject { class InputState: NSObject {
/// Represents that the input controller is deactivated. /// Represents that the input controller is deactivated.
@objc (InputStateDeactivated) @objc(InputStateDeactivated)
class Deactivated: InputState { class Deactivated: InputState {
override var description: String { override var description: String {
"<InputState.Deactivated>" "<InputState.Deactivated>"
} }
} }
// MARK: - // MARK: -
/// Represents that the composing buffer is empty. /// Represents that the composing buffer is empty.
@objc (InputStateEmpty) @objc(InputStateEmpty)
class Empty: InputState { class Empty: InputState {
@objc var composingBuffer: String { @objc var composingBuffer: String {
"" ""
} }
override var description: String { override var description: String {
"<InputState.Empty>" "<InputState.Empty>"
} }
} }
// MARK: - // MARK: -
/// Represents that the composing buffer is empty. /// Represents that the composing buffer is empty.
@objc (InputStateEmptyIgnoringPreviousState) @objc(InputStateEmptyIgnoringPreviousState)
class EmptyIgnoringPreviousState: InputState { class EmptyIgnoringPreviousState: InputState {
@objc var composingBuffer: String { @objc var composingBuffer: String {
"" ""
} }
override var description: String { override var description: String {
"<InputState.EmptyIgnoringPreviousState>" "<InputState.EmptyIgnoringPreviousState>"
} }
} }
// MARK: - // MARK: -
/// Represents that the input controller is committing text into client app. /// Represents that the input controller is committing text into client app.
@objc (InputStateCommitting) @objc(InputStateCommitting)
class Committing: InputState { class Committing: InputState {
@objc private(set) var poppedText: String = "" @objc private(set) var poppedText: String = ""
@objc convenience init(poppedText: String) { @objc convenience init(poppedText: String) {
self.init() self.init()
self.poppedText = poppedText self.poppedText = poppedText
} }
override var description: String { override var description: String {
"<InputState.Committing poppedText:\(poppedText)>" "<InputState.Committing poppedText:\(poppedText)>"
} }
} }
// MARK: - // MARK: -
/// Represents that the composing buffer is not empty. /// Represents that the composing buffer is not empty.
@objc (InputStateNotEmpty) @objc(InputStateNotEmpty)
class NotEmpty: InputState { class NotEmpty: InputState {
@objc private(set) var composingBuffer: String @objc private(set) var composingBuffer: String
@objc private(set) var cursorIndex: UInt @objc private(set) var cursorIndex: UInt
@objc init(composingBuffer: String, cursorIndex: UInt) { @objc init(composingBuffer: String, cursorIndex: UInt) {
self.composingBuffer = composingBuffer self.composingBuffer = composingBuffer
self.cursorIndex = cursorIndex self.cursorIndex = cursorIndex
} }
override var description: String { override var description: String {
"<InputState.NotEmpty, composingBuffer:\(composingBuffer), cursorIndex:\(cursorIndex)>" "<InputState.NotEmpty, composingBuffer:\(composingBuffer), cursorIndex:\(cursorIndex)>"
} }
} }
// MARK: - // MARK: -
/// Represents that the user is inputting text. /// Represents that the user is inputting text.
@objc (InputStateInputting) @objc(InputStateInputting)
class Inputting: NotEmpty { class Inputting: NotEmpty {
@objc var poppedText: String = "" @objc var poppedText: String = ""
@objc var tooltip: String = "" @objc var tooltip: String = ""
@objc override init(composingBuffer: String, cursorIndex: UInt) { @objc override init(composingBuffer: String, cursorIndex: UInt) {
super.init(composingBuffer: composingBuffer, cursorIndex: cursorIndex) super.init(composingBuffer: composingBuffer, cursorIndex: cursorIndex)
} }
@objc var attributedString: NSAttributedString { @objc var attributedString: NSAttributedString {
let attributedSting = NSAttributedString(string: composingBuffer, attributes: [ let attributedSting = NSAttributedString(
.underlineStyle: NSUnderlineStyle.single.rawValue, string: composingBuffer,
.markedClauseSegment: 0 attributes: [
]) .underlineStyle: NSUnderlineStyle.single.rawValue,
return attributedSting .markedClauseSegment: 0,
} ])
return attributedSting
}
override var description: String { override var description: String {
"<InputState.Inputting, composingBuffer:\(composingBuffer), cursorIndex:\(cursorIndex)>, poppedText:\(poppedText)>" "<InputState.Inputting, composingBuffer:\(composingBuffer), cursorIndex:\(cursorIndex)>, poppedText:\(poppedText)>"
} }
} }
// MARK: - // MARK: -
private let kMinMarkRangeLength = 2 private let kMinMarkRangeLength = 2
private let kMaxMarkRangeLength = mgrPrefs.maxCandidateLength private let kMaxMarkRangeLength = mgrPrefs.maxCandidateLength
/// Represents that the user is marking a range in the composing buffer. /// Represents that the user is marking a range in the composing buffer.
@objc (InputStateMarking) @objc(InputStateMarking)
class Marking: NotEmpty { class Marking: NotEmpty {
@objc private(set) var markerIndex: UInt @objc private(set) var markerIndex: UInt
@objc private(set) var markedRange: NSRange @objc private(set) var markedRange: NSRange
@objc private var deleteTargetExists = false @objc private var deleteTargetExists = false
@objc var tooltip: String { @objc var tooltip: String {
if composingBuffer.count != readings.count { if composingBuffer.count != readings.count {
TooltipController.backgroundColor = NSColor(red: 0.55, green: 0.00, blue: 0.00, alpha: 1.00) TooltipController.backgroundColor = NSColor(
TooltipController.textColor = NSColor.white red: 0.55, green: 0.00, blue: 0.00, alpha: 1.00)
return NSLocalizedString("⚠︎ Unhandlable: Chars and Readings in buffer doesn't match.", comment: "") TooltipController.textColor = NSColor.white
} return NSLocalizedString(
"⚠︎ Unhandlable: Chars and Readings in buffer doesn't match.", comment: "")
}
if mgrPrefs.phraseReplacementEnabled { if mgrPrefs.phraseReplacementEnabled {
TooltipController.backgroundColor = NSColor.purple TooltipController.backgroundColor = NSColor.purple
TooltipController.textColor = NSColor.white TooltipController.textColor = NSColor.white
return NSLocalizedString("⚠︎ Phrase replacement mode enabled, interfering user phrase entry.", comment: "") return NSLocalizedString(
} "⚠︎ Phrase replacement mode enabled, interfering user phrase entry.", comment: ""
if markedRange.length == 0 { )
return "" }
} if markedRange.length == 0 {
return ""
}
let text = (composingBuffer as NSString).substring(with: markedRange) let text = (composingBuffer as NSString).substring(with: markedRange)
if markedRange.length < kMinMarkRangeLength { if markedRange.length < kMinMarkRangeLength {
TooltipController.backgroundColor = NSColor(red: 0.18, green: 0.18, blue: 0.18, alpha: 1.00) TooltipController.backgroundColor = NSColor(
TooltipController.textColor = NSColor(red: 0.86, green: 0.86, blue: 0.86, alpha: 1.00) red: 0.18, green: 0.18, blue: 0.18, alpha: 1.00)
return String(format: NSLocalizedString("\"%@\" length must ≥ 2 for a user phrase.", comment: ""), text) TooltipController.textColor = NSColor(
} else if (markedRange.length > kMaxMarkRangeLength) { red: 0.86, green: 0.86, blue: 0.86, alpha: 1.00)
TooltipController.backgroundColor = NSColor(red: 0.26, green: 0.16, blue: 0.00, alpha: 1.00) return String(
TooltipController.textColor = NSColor(red: 1.00, green: 0.60, blue: 0.00, alpha: 1.00) format: NSLocalizedString(
return String(format: NSLocalizedString("\"%@\" length should ≤ %d for a user phrase.", comment: ""), text, kMaxMarkRangeLength) "\"%@\" 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 (exactBegin, _) = (composingBuffer as NSString).characterIndex(
let (exactEnd, _) = (composingBuffer as NSString).characterIndex(from: markedRange.location + markedRange.length) from: markedRange.location)
let selectedReadings = readings[exactBegin..<exactEnd] let (exactEnd, _) = (composingBuffer as NSString).characterIndex(
let joined = selectedReadings.joined(separator: "-") from: markedRange.location + markedRange.length)
let exist = mgrLangModel.checkIfUserPhraseExist(userPhrase: text, mode: ctlInputMethod.currentKeyHandler.inputMode, key: joined) let selectedReadings = readings[exactBegin..<exactEnd]
if exist { let joined = selectedReadings.joined(separator: "-")
deleteTargetExists = exist let exist = mgrLangModel.checkIfUserPhraseExist(
TooltipController.backgroundColor = NSColor(red: 0.00, green: 0.18, blue: 0.13, alpha: 1.00) userPhrase: text, mode: ctlInputMethod.currentKeyHandler.inputMode, key: joined)
TooltipController.textColor = NSColor(red: 0.00, green: 1.00, blue: 0.74, alpha: 1.00) if exist {
return String(format: NSLocalizedString("\"%@\" already exists: ↩ to boost, ⇧⌘↩ to exclude.", comment: ""), text) deleteTargetExists = exist
} TooltipController.backgroundColor = NSColor(
TooltipController.backgroundColor = NSColor(red: 0.18, green: 0.18, blue: 0.18, alpha: 1.00) red: 0.00, green: 0.18, blue: 0.13, alpha: 1.00)
TooltipController.textColor = NSColor.white TooltipController.textColor = NSColor(
return String(format: NSLocalizedString("\"%@\" selected. ↩ to add user phrase.", comment: ""), text) red: 0.00, green: 1.00, blue: 0.74, alpha: 1.00)
} return String(
format: NSLocalizedString(
"\"%@\" already exists: ↩ to boost, ⇧⌘↩ to exclude.", comment: ""), text
)
}
TooltipController.backgroundColor = NSColor(
red: 0.18, green: 0.18, blue: 0.18, alpha: 1.00)
TooltipController.textColor = NSColor.white
return String(
format: NSLocalizedString("\"%@\" selected. ↩ to add user phrase.", comment: ""),
text)
}
@objc var tooltipForInputting: String = "" @objc var tooltipForInputting: String = ""
@objc private(set) var readings: [String] @objc private(set) var readings: [String]
@objc init(composingBuffer: String, cursorIndex: UInt, markerIndex: UInt, readings: [String]) { @objc init(composingBuffer: String, cursorIndex: UInt, markerIndex: UInt, readings: [String]) {
self.markerIndex = markerIndex self.markerIndex = markerIndex
let begin = min(cursorIndex, markerIndex) let begin = min(cursorIndex, markerIndex)
let end = max(cursorIndex, markerIndex) let end = max(cursorIndex, markerIndex)
markedRange = NSMakeRange(Int(begin), Int(end - begin)) markedRange = NSMakeRange(Int(begin), Int(end - begin))
self.readings = readings self.readings = readings
super.init(composingBuffer: composingBuffer, cursorIndex: cursorIndex) super.init(composingBuffer: composingBuffer, cursorIndex: cursorIndex)
} }
@objc var attributedString: NSAttributedString { @objc var attributedString: NSAttributedString {
let attributedSting = NSMutableAttributedString(string: composingBuffer) let attributedSting = NSMutableAttributedString(string: composingBuffer)
let end = markedRange.location + markedRange.length let end = markedRange.location + markedRange.length
attributedSting.setAttributes([ attributedSting.setAttributes(
.underlineStyle: NSUnderlineStyle.single.rawValue, [
.markedClauseSegment: 0 .underlineStyle: NSUnderlineStyle.single.rawValue,
], range: NSRange(location: 0, length: markedRange.location)) .markedClauseSegment: 0,
attributedSting.setAttributes([ ], range: NSRange(location: 0, length: markedRange.location))
.underlineStyle: NSUnderlineStyle.thick.rawValue, attributedSting.setAttributes(
.markedClauseSegment: 1 [
], range: markedRange) .underlineStyle: NSUnderlineStyle.thick.rawValue,
attributedSting.setAttributes([ .markedClauseSegment: 1,
.underlineStyle: NSUnderlineStyle.single.rawValue, ], range: markedRange)
.markedClauseSegment: 2 attributedSting.setAttributes(
], range: NSRange(location: end, [
length: (composingBuffer as NSString).length - end)) .underlineStyle: NSUnderlineStyle.single.rawValue,
return attributedSting .markedClauseSegment: 2,
} ],
range: NSRange(
location: end,
length: (composingBuffer as NSString).length - end))
return attributedSting
}
override var description: String { override var description: String {
"<InputState.Marking, composingBuffer:\(composingBuffer), cursorIndex:\(cursorIndex), markedRange:\(markedRange)>" "<InputState.Marking, composingBuffer:\(composingBuffer), cursorIndex:\(cursorIndex), markedRange:\(markedRange)>"
} }
@objc func convertToInputting() -> Inputting { @objc func convertToInputting() -> Inputting {
let state = Inputting(composingBuffer: composingBuffer, cursorIndex: cursorIndex) let state = Inputting(composingBuffer: composingBuffer, cursorIndex: cursorIndex)
state.tooltip = tooltipForInputting state.tooltip = tooltipForInputting
return state return state
} }
@objc var validToWrite: Bool { @objc var validToWrite: Bool {
/// vChewing allows users to input a string whose length differs /// vChewing allows users to input a string whose length differs
/// from the amount of Bopomofo readings. In this case, the range /// from the amount of Bopomofo readings. In this case, the range
/// in the composing buffer and the readings could not match, so /// in the composing buffer and the readings could not match, so
/// we disable the function to write user phrases in this case. /// we disable the function to write user phrases in this case.
if composingBuffer.count != readings.count { if composingBuffer.count != readings.count {
return false return false
} }
if markedRange.length < kMinMarkRangeLength { if markedRange.length < kMinMarkRangeLength {
return false return false
} }
if markedRange.length > kMaxMarkRangeLength { if markedRange.length > kMaxMarkRangeLength {
return false return false
} }
if ctlInputMethod.areWeDeleting && !deleteTargetExists { if ctlInputMethod.areWeDeleting && !deleteTargetExists {
return false return false
} }
return markedRange.length >= kMinMarkRangeLength && markedRange.length <= kMaxMarkRangeLength return markedRange.length >= kMinMarkRangeLength
} && markedRange.length <= kMaxMarkRangeLength
}
@objc var chkIfUserPhraseExists: Bool { @objc var chkIfUserPhraseExists: Bool {
let text = (composingBuffer as NSString).substring(with: markedRange) let text = (composingBuffer as NSString).substring(with: markedRange)
let (exactBegin, _) = (composingBuffer as NSString).characterIndex(from: markedRange.location) let (exactBegin, _) = (composingBuffer as NSString).characterIndex(
let (exactEnd, _) = (composingBuffer as NSString).characterIndex(from: markedRange.location + markedRange.length) from: markedRange.location)
let selectedReadings = readings[exactBegin..<exactEnd] let (exactEnd, _) = (composingBuffer as NSString).characterIndex(
let joined = selectedReadings.joined(separator: "-") from: markedRange.location + markedRange.length)
return mgrLangModel.checkIfUserPhraseExist(userPhrase: text, mode: ctlInputMethod.currentKeyHandler.inputMode, key: joined) == true let selectedReadings = readings[exactBegin..<exactEnd]
} let joined = selectedReadings.joined(separator: "-")
return mgrLangModel.checkIfUserPhraseExist(
userPhrase: text, mode: ctlInputMethod.currentKeyHandler.inputMode, key: joined)
== true
}
@objc var userPhrase: String { @objc var userPhrase: String {
let text = (composingBuffer as NSString).substring(with: markedRange) let text = (composingBuffer as NSString).substring(with: markedRange)
let (exactBegin, _) = (composingBuffer as NSString).characterIndex(from: markedRange.location) let (exactBegin, _) = (composingBuffer as NSString).characterIndex(
let (exactEnd, _) = (composingBuffer as NSString).characterIndex(from: markedRange.location + markedRange.length) from: markedRange.location)
let selectedReadings = readings[exactBegin..<exactEnd] let (exactEnd, _) = (composingBuffer as NSString).characterIndex(
let joined = selectedReadings.joined(separator: "-") from: markedRange.location + markedRange.length)
return "\(text) \(joined)" let selectedReadings = readings[exactBegin..<exactEnd]
} let joined = selectedReadings.joined(separator: "-")
return "\(text) \(joined)"
}
@objc var userPhraseConverted: String { @objc var userPhraseConverted: String {
let text = OpenCCBridge.crossConvert((composingBuffer as NSString).substring(with: markedRange)) ?? "" let text =
let (exactBegin, _) = (composingBuffer as NSString).characterIndex(from: markedRange.location) OpenCCBridge.crossConvert(
let (exactEnd, _) = (composingBuffer as NSString).characterIndex(from: markedRange.location + markedRange.length) (composingBuffer as NSString).substring(with: markedRange)) ?? ""
let selectedReadings = readings[exactBegin..<exactEnd] let (exactBegin, _) = (composingBuffer as NSString).characterIndex(
let joined = selectedReadings.joined(separator: "-") from: markedRange.location)
let convertedMark = "#𝙊𝙥𝙚𝙣𝘾𝘾" let (exactEnd, _) = (composingBuffer as NSString).characterIndex(
return "\(text) \(joined)\t\(convertedMark)" from: markedRange.location + markedRange.length)
} let selectedReadings = readings[exactBegin..<exactEnd]
} let joined = selectedReadings.joined(separator: "-")
let convertedMark = "#𝙊𝙥𝙚𝙣𝘾𝘾"
return "\(text) \(joined)\t\(convertedMark)"
}
}
// MARK: - // MARK: -
/// Represents that the user is choosing in a candidates list. /// Represents that the user is choosing in a candidates list.
@objc (InputStateChoosingCandidate) @objc(InputStateChoosingCandidate)
class ChoosingCandidate: NotEmpty { class ChoosingCandidate: NotEmpty {
@objc private(set) var candidates: [String] @objc private(set) var candidates: [String]
@objc private(set) var useVerticalMode: Bool @objc private(set) var useVerticalMode: Bool
@objc init(composingBuffer: String, cursorIndex: UInt, candidates: [String], useVerticalMode: Bool) { @objc init(composingBuffer: String, cursorIndex: UInt, candidates: [String], useVerticalMode: Bool) {
self.candidates = candidates self.candidates = candidates
self.useVerticalMode = useVerticalMode self.useVerticalMode = useVerticalMode
super.init(composingBuffer: composingBuffer, cursorIndex: cursorIndex) super.init(composingBuffer: composingBuffer, cursorIndex: cursorIndex)
} }
@objc var attributedString: NSAttributedString { @objc var attributedString: NSAttributedString {
let attributedSting = NSAttributedString(string: composingBuffer, attributes: [ let attributedSting = NSAttributedString(
.underlineStyle: NSUnderlineStyle.single.rawValue, string: composingBuffer,
.markedClauseSegment: 0 attributes: [
]) .underlineStyle: NSUnderlineStyle.single.rawValue,
return attributedSting .markedClauseSegment: 0,
} ])
return attributedSting
}
override var description: String { override var description: String {
"<InputState.ChoosingCandidate, candidates:\(candidates), useVerticalMode:\(useVerticalMode), composingBuffer:\(composingBuffer), cursorIndex:\(cursorIndex)>" "<InputState.ChoosingCandidate, candidates:\(candidates), useVerticalMode:\(useVerticalMode), composingBuffer:\(composingBuffer), cursorIndex:\(cursorIndex)>"
} }
} }
// MARK: - // MARK: -
/// Represents that the user is choosing in a candidates list /// Represents that the user is choosing in a candidates list
/// in the associated phrases mode. /// in the associated phrases mode.
@objc (InputStateAssociatedPhrases) @objc(InputStateAssociatedPhrases)
class AssociatedPhrases: InputState { class AssociatedPhrases: InputState {
@objc private(set) var candidates: [String] = [] @objc private(set) var candidates: [String] = []
@objc private(set) var useVerticalMode: Bool = false @objc private(set) var useVerticalMode: Bool = false
@objc init(candidates: [String], useVerticalMode: Bool) { @objc init(candidates: [String], useVerticalMode: Bool) {
self.candidates = candidates self.candidates = candidates
self.useVerticalMode = useVerticalMode self.useVerticalMode = useVerticalMode
super.init() super.init()
} }
override var description: String { override var description: String {
"<InputState.AssociatedPhrases, candidates:\(candidates), useVerticalMode:\(useVerticalMode)>" "<InputState.AssociatedPhrases, candidates:\(candidates), useVerticalMode:\(useVerticalMode)>"
} }
} }
@objc (InputStateSymbolTable) @objc(InputStateSymbolTable)
class SymbolTable: ChoosingCandidate { class SymbolTable: ChoosingCandidate {
@objc var node: SymbolNode @objc var node: SymbolNode
@objc init(node: SymbolNode, useVerticalMode: Bool) { @objc init(node: SymbolNode, useVerticalMode: Bool) {
self.node = node self.node = node
let candidates = node.children?.map { $0.title } ?? [String]() let candidates = node.children?.map { $0.title } ?? [String]()
super.init(composingBuffer: "", cursorIndex: 0, candidates: candidates, useVerticalMode: useVerticalMode) super.init(
} composingBuffer: "", cursorIndex: 0, candidates: candidates,
useVerticalMode: useVerticalMode)
}
override var description: String { override var description: String {
"<InputState.SymbolTable, candidates:\(candidates), useVerticalMode:\(useVerticalMode), composingBuffer:\(composingBuffer), cursorIndex:\(cursorIndex)>" "<InputState.SymbolTable, candidates:\(candidates), useVerticalMode:\(useVerticalMode), composingBuffer:\(composingBuffer), cursorIndex:\(cursorIndex)>"
} }
} }
} }
@objc class SymbolNode: NSObject { @objc class SymbolNode: NSObject {
@objc var title: String @objc var title: String
@objc var children: [SymbolNode]? @objc var children: [SymbolNode]?
@objc init(_ title: String, _ children: [SymbolNode]? = nil) { @objc init(_ title: String, _ children: [SymbolNode]? = nil) {
self.title = title self.title = title
self.children = children self.children = children
super.init() super.init()
} }
@objc init(_ title: String, symbols: String) { @objc init(_ title: String, symbols: String) {
self.title = title self.title = title
self.children = Array(symbols).map { SymbolNode(String($0), nil) } self.children = Array(symbols).map { SymbolNode(String($0), nil) }
super.init() super.init()
} }
@objc static let catCommonSymbols = String(format: NSLocalizedString("catCommonSymbols", comment: "")) @objc static let catCommonSymbols = String(
@objc static let catHoriBrackets = String(format: NSLocalizedString("catHoriBrackets", comment: "")) format: NSLocalizedString("catCommonSymbols", comment: ""))
@objc static let catVertBrackets = String(format: NSLocalizedString("catVertBrackets", comment: "")) @objc static let catHoriBrackets = String(
@objc static let catGreekLetters = String(format: NSLocalizedString("catGreekLetters", comment: "")) format: NSLocalizedString("catHoriBrackets", comment: ""))
@objc static let catMathSymbols = String(format: NSLocalizedString("catMathSymbols", comment: "")) @objc static let catVertBrackets = String(
@objc static let catCurrencyUnits = String(format: NSLocalizedString("catCurrencyUnits", comment: "")) format: NSLocalizedString("catVertBrackets", comment: ""))
@objc static let catSpecialSymbols = String(format: NSLocalizedString("catSpecialSymbols", comment: "")) @objc static let catGreekLetters = String(
@objc static let catUnicodeSymbols = String(format: NSLocalizedString("catUnicodeSymbols", comment: "")) format: NSLocalizedString("catGreekLetters", comment: ""))
@objc static let catCircledKanjis = String(format: NSLocalizedString("catCircledKanjis", comment: "")) @objc static let catMathSymbols = String(
@objc static let catCircledKataKana = String(format: NSLocalizedString("catCircledKataKana", comment: "")) format: NSLocalizedString("catMathSymbols", comment: ""))
@objc static let catBracketKanjis = String(format: NSLocalizedString("catBracketKanjis", comment: "")) @objc static let catCurrencyUnits = String(
@objc static let catSingleTableLines = String(format: NSLocalizedString("catSingleTableLines", comment: "")) format: NSLocalizedString("catCurrencyUnits", comment: ""))
@objc static let catDoubleTableLines = String(format: NSLocalizedString("catDoubleTableLines", comment: "")) @objc static let catSpecialSymbols = String(
@objc static let catFillingBlocks = String(format: NSLocalizedString("catFillingBlocks", comment: "")) format: NSLocalizedString("catSpecialSymbols", comment: ""))
@objc static let catLineSegments = String(format: NSLocalizedString("catLineSegments", comment: "")) @objc static let catUnicodeSymbols = String(
format: NSLocalizedString("catUnicodeSymbols", comment: ""))
@objc static let root: SymbolNode = SymbolNode("/", [ @objc static let catCircledKanjis = String(
SymbolNode(""), format: NSLocalizedString("catCircledKanjis", comment: ""))
SymbolNode(catCommonSymbols, symbols:",、。.?!;:‧‥﹐﹒˙·‘’“”〝〞‵′〃~$%@&#*"), @objc static let catCircledKataKana = String(
SymbolNode(catHoriBrackets, symbols:"()「」〔〕{}〈〉『』《》【】﹙﹚﹝﹞﹛﹜"), format: NSLocalizedString("catCircledKataKana", comment: ""))
SymbolNode(catVertBrackets, symbols:"︵︶﹁﹂︹︺︷︸︿﹀﹃﹄︽︾︻︼"), @objc static let catBracketKanjis = String(
SymbolNode(catGreekLetters, symbols:"αβγδεζηθικλμνξοπρστυφχψωΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ"), format: NSLocalizedString("catBracketKanjis", comment: ""))
SymbolNode(catMathSymbols, symbols:"+-×÷=≠≒∞±√<>﹤﹥≦≧∩∪ˇ⊥∠∟⊿㏒㏑∫∮∵∴╳﹢"), @objc static let catSingleTableLines = String(
SymbolNode(catCurrencyUnits, symbols:"$€¥¢£₽₨₩฿₺₮₱₭₴₦৲৳૱௹﷼₹₲₪₡₫៛₵₢₸₤₳₥₠₣₰₧₯₶₷"), format: NSLocalizedString("catSingleTableLines", comment: ""))
SymbolNode(catSpecialSymbols, symbols:"↑↓←→↖↗↙↘↺⇧⇩⇦⇨⇄⇆⇅⇵↻◎○●⊕⊙※△▲☆★◇◆□■▽▼§¥〒¢£♀♂↯"), @objc static let catDoubleTableLines = String(
SymbolNode(catUnicodeSymbols, symbols:"♨☀☁☂☃♠♥♣♦♩♪♫♬☺☻"), format: NSLocalizedString("catDoubleTableLines", comment: ""))
SymbolNode(catCircledKanjis, symbols:"㊟㊞㊚㊛㊊㊋㊌㊍㊎㊏㊐㊑㊒㊓㊔㊕㊖㊗︎㊘㊙︎㊜㊝㊠㊡㊢㊣㊤㊥㊦㊧㊨㊩㊪㊫㊬㊭㊮㊯㊰🈚︎🈯︎"), @objc static let catFillingBlocks = String(
SymbolNode(catCircledKataKana, symbols:"㋐㋑㋒㋓㋔㋕㋖㋗㋘㋙㋚㋛㋜㋝㋞㋟㋠㋡㋢㋣㋤㋥㋦㋧㋨㋩㋪㋫㋬㋭㋮㋯㋰㋱㋲㋳㋴㋵㋶㋷㋸㋹㋺㋻㋼㋾"), format: NSLocalizedString("catFillingBlocks", comment: ""))
SymbolNode(catBracketKanjis, symbols:"㈪㈫㈬㈭㈮㈯㈰㈱㈲㈳㈴㈵㈶㈷㈸㈹㈺㈻㈼㈽㈾㈿㉀㉁㉂㉃"), @objc static let catLineSegments = String(
SymbolNode(catSingleTableLines, symbols:"├─┼┴┬┤┌┐╞═╪╡│▕└┘╭╮╰╯"), format: NSLocalizedString("catLineSegments", comment: ""))
SymbolNode(catDoubleTableLines, symbols:"╔╦╗╠═╬╣╓╥╖╒╤╕║╚╩╝╟╫╢╙╨╜╞╪╡╘╧╛"),
SymbolNode(catFillingBlocks, symbols:"_ˍ▁▂▃▄▅▆▇█▏▎▍▌▋▊▉◢◣◥◤"), @objc static let root: SymbolNode = SymbolNode(
SymbolNode(catLineSegments, symbols:"﹣﹦≡|∣∥–︱—︳╴¯ ̄﹉﹊﹍﹎﹋﹌﹏︴∕﹨╱╲/\"), "/",
]) [
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: "﹣﹦≡|∣∥–︱—︳╴¯ ̄﹉﹊﹍﹎﹋﹌﹏︴∕﹨╱╲/\"),
])
} }

View File

@ -1,20 +1,27 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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/Foundation.h> #import <Foundation/Foundation.h>
@ -32,8 +39,8 @@ extern InputMode imeModeNULL;
@class KeyHandler; @class KeyHandler;
@protocol KeyHandlerDelegate <NSObject> @protocol KeyHandlerDelegate <NSObject>
- (id)candidateControllerForKeyHandler:(KeyHandler *)keyHandler; - (id)ctlCandidateForKeyHandler:(KeyHandler *)keyHandler;
- (void)keyHandler:(KeyHandler *)keyHandler didSelectCandidateAtIndex:(NSInteger)index candidateController:(id)controller; - (void)keyHandler:(KeyHandler *)keyHandler didSelectCandidateAtIndex:(NSInteger)index ctlCandidate:(id)controller;
- (BOOL)keyHandler:(KeyHandler *)keyHandler didRequestWriteUserPhraseWithState:(InputState *)state; - (BOOL)keyHandler:(KeyHandler *)keyHandler didRequestWriteUserPhraseWithState:(InputState *)state;
@end @end
@ -43,7 +50,8 @@ extern InputMode imeModeNULL;
- (BOOL)handleInput:(keyParser *)input - (BOOL)handleInput:(keyParser *)input
state:(InputState *)state state:(InputState *)state
stateCallback:(void (^)(InputState *))stateCallback 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)syncWithPreferences;
- (void)fixNodeWithValue:(NSString *)value NS_SWIFT_NAME(fixNode(value:)); - (void)fixNodeWithValue:(NSString *)value NS_SWIFT_NAME(fixNode(value:));
@ -52,8 +60,8 @@ extern InputMode imeModeNULL;
- (InputState *)buildInputtingState; - (InputState *)buildInputtingState;
- (nullable InputState *)buildAssociatePhraseStateWithKey:(NSString *)key useVerticalMode:(BOOL)useVerticalMode; - (nullable InputState *)buildAssociatePhraseStateWithKey:(NSString *)key useVerticalMode:(BOOL)useVerticalMode;
@property (strong, nonatomic) InputMode inputMode; @property(strong, nonatomic) InputMode inputMode;
@property (weak, nonatomic) id <KeyHandlerDelegate> delegate; @property(weak, nonatomic) id<KeyHandlerDelegate> delegate;
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

File diff suppressed because it is too large Load Diff

View File

@ -1,20 +1,27 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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 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. // 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 // KeyCodes: https://eastmanreference.com/complete-list-of-applescript-key-codes
@objc enum KeyCode: UInt16 { @objc enum KeyCode: UInt16 {
case none = 0 case none = 0
case space = 49 case space = 49
case backSpace = 51 case backSpace = 51
case esc = 53 case esc = 53
case tab = 48 case tab = 48
case enterLF = 76 case enterLF = 76
case enterCR = 36 case enterCR = 36
case up = 126 case up = 126
case down = 125 case down = 125
case left = 123 case left = 123
case right = 124 case right = 124
case pageUp = 116 case pageUp = 116
case pageDown = 121 case pageDown = 121
case home = 115 case home = 115
case end = 119 case end = 119
case delete = 117 case delete = 117
case leftShift = 56 case leftShift = 56
case rightShift = 60 case rightShift = 60
case capsLock = 57 case capsLock = 57
case symbolMenuPhysicalKey = 50 case symbolMenuPhysicalKey = 50
} }
// CharCodes: https://theasciicode.com.ar/ascii-control-characters/horizontal-tab-ascii-code-9.html // CharCodes: https://theasciicode.com.ar/ascii-control-characters/horizontal-tab-ascii-code-9.html
enum CharCode: UInt/*16*/ { enum CharCode: UInt /*16*/ {
case yajuusenpai = 1145141919810893 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. // - 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 { class keyParser: NSObject {
@objc private (set) var useVerticalMode: Bool @objc private(set) var useVerticalMode: Bool
@objc private (set) var inputText: String? @objc private(set) var inputText: String?
@objc private (set) var inputTextIgnoringModifiers: String? @objc private(set) var inputTextIgnoringModifiers: String?
@objc private (set) var charCode: UInt16 @objc private(set) var charCode: UInt16
@objc private (set) var keyCode: UInt16 @objc private(set) var keyCode: UInt16
private var isFlagChanged: Bool private var isFlagChanged: Bool
private var flags: NSEvent.ModifierFlags private var flags: NSEvent.ModifierFlags
private var cursorForwardKey: KeyCode private var cursorForwardKey: KeyCode
private var cursorBackwardKey: KeyCode private var cursorBackwardKey: KeyCode
private var extraChooseCandidateKey: KeyCode private var extraChooseCandidateKey: KeyCode
private var extraChooseCandidateKeyReverse: KeyCode private var extraChooseCandidateKeyReverse: KeyCode
private var absorbedArrowKey: KeyCode private var absorbedArrowKey: KeyCode
private var verticalModeOnlyChooseCandidateKey: KeyCode private var verticalModeOnlyChooseCandidateKey: KeyCode
@objc private (set) var emacsKey: vChewingEmacsKey @objc private(set) var emacsKey: vChewingEmacsKey
@objc init(inputText: String?, keyCode: UInt16, charCode: UInt16, flags: NSEvent.ModifierFlags, isVerticalMode: Bool, inputTextIgnoringModifiers: String? = nil) { @objc init(
let inputText = AppleKeyboardConverter.cnvStringApple2ABC(inputText ?? "") inputText: String?, keyCode: UInt16, charCode: UInt16, flags: NSEvent.ModifierFlags,
let inputTextIgnoringModifiers = AppleKeyboardConverter.cnvStringApple2ABC(inputTextIgnoringModifiers ?? inputText) isVerticalMode: Bool, inputTextIgnoringModifiers: String? = nil
self.inputText = inputText ) {
self.inputTextIgnoringModifiers = inputTextIgnoringModifiers let inputText = AppleKeyboardConverter.cnvStringApple2ABC(inputText ?? "")
self.keyCode = keyCode let inputTextIgnoringModifiers = AppleKeyboardConverter.cnvStringApple2ABC(
self.charCode = AppleKeyboardConverter.cnvApple2ABC(charCode) inputTextIgnoringModifiers ?? inputText)
self.flags = flags self.inputText = inputText
self.isFlagChanged = false self.inputTextIgnoringModifiers = inputTextIgnoringModifiers
useVerticalMode = isVerticalMode self.keyCode = keyCode
emacsKey = EmacsKeyHelper.detect(charCode: AppleKeyboardConverter.cnvApple2ABC(charCode), flags: flags) self.charCode = AppleKeyboardConverter.cnvApple2ABC(charCode)
cursorForwardKey = useVerticalMode ? .down : .right self.flags = flags
cursorBackwardKey = useVerticalMode ? .up : .left self.isFlagChanged = false
extraChooseCandidateKey = useVerticalMode ? .left : .down useVerticalMode = isVerticalMode
extraChooseCandidateKeyReverse = useVerticalMode ? .right : .up emacsKey = EmacsKeyHelper.detect(
absorbedArrowKey = useVerticalMode ? .right : .up charCode: AppleKeyboardConverter.cnvApple2ABC(charCode), flags: flags)
verticalModeOnlyChooseCandidateKey = useVerticalMode ? absorbedArrowKey : .none cursorForwardKey = useVerticalMode ? .down : .right
super.init() 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) { @objc init(event: NSEvent, isVerticalMode: Bool) {
inputText = AppleKeyboardConverter.cnvStringApple2ABC(event.characters ?? "") inputText = AppleKeyboardConverter.cnvStringApple2ABC(event.characters ?? "")
inputTextIgnoringModifiers = AppleKeyboardConverter.cnvStringApple2ABC(event.charactersIgnoringModifiers ?? "") inputTextIgnoringModifiers = AppleKeyboardConverter.cnvStringApple2ABC(
keyCode = event.keyCode event.charactersIgnoringModifiers ?? "")
flags = event.modifierFlags keyCode = event.keyCode
isFlagChanged = (event.type == .flagsChanged) ? true : false flags = event.modifierFlags
useVerticalMode = isVerticalMode isFlagChanged = (event.type == .flagsChanged) ? true : false
let charCode: UInt16 = { useVerticalMode = isVerticalMode
guard let inputText = event.characters, inputText.count > 0 else { let charCode: UInt16 = {
return 0 guard let inputText = event.characters, inputText.count > 0 else {
} return 0
let first = inputText[inputText.startIndex].utf16.first! }
return first let first = inputText[inputText.startIndex].utf16.first!
}() return first
self.charCode = AppleKeyboardConverter.cnvApple2ABC(charCode) }()
emacsKey = EmacsKeyHelper.detect(charCode: AppleKeyboardConverter.cnvApple2ABC(charCode), flags: event.modifierFlags) self.charCode = AppleKeyboardConverter.cnvApple2ABC(charCode)
cursorForwardKey = useVerticalMode ? .down : .right emacsKey = EmacsKeyHelper.detect(
cursorBackwardKey = useVerticalMode ? .up : .left charCode: AppleKeyboardConverter.cnvApple2ABC(charCode), flags: event.modifierFlags)
extraChooseCandidateKey = useVerticalMode ? .left : .down cursorForwardKey = useVerticalMode ? .down : .right
extraChooseCandidateKeyReverse = useVerticalMode ? .right : .up cursorBackwardKey = useVerticalMode ? .up : .left
absorbedArrowKey = useVerticalMode ? .right : .up extraChooseCandidateKey = useVerticalMode ? .left : .down
verticalModeOnlyChooseCandidateKey = useVerticalMode ? absorbedArrowKey : .none extraChooseCandidateKeyReverse = useVerticalMode ? .right : .up
super.init() absorbedArrowKey = useVerticalMode ? .right : .up
} verticalModeOnlyChooseCandidateKey = useVerticalMode ? absorbedArrowKey : .none
super.init()
}
override var description: String { override var description: String {
charCode = AppleKeyboardConverter.cnvApple2ABC(charCode) charCode = AppleKeyboardConverter.cnvApple2ABC(charCode)
inputText = AppleKeyboardConverter.cnvStringApple2ABC(inputText ?? "") inputText = AppleKeyboardConverter.cnvStringApple2ABC(inputText ?? "")
inputTextIgnoringModifiers = AppleKeyboardConverter.cnvStringApple2ABC(inputTextIgnoringModifiers ?? "") inputTextIgnoringModifiers = AppleKeyboardConverter.cnvStringApple2ABC(
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)>" 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 { @objc var isShiftHold: Bool {
flags.contains([.shift]) flags.contains([.shift])
} }
@objc var isCommandHold: Bool { @objc var isCommandHold: Bool {
flags.contains([.command]) flags.contains([.command])
} }
@objc var isControlHold: Bool { @objc var isControlHold: Bool {
flags.contains([.control]) flags.contains([.control])
} }
@objc var isControlHotKey: Bool { @objc var isControlHotKey: Bool {
flags.contains([.control]) && inputText?.first?.isLetter ?? false flags.contains([.control]) && inputText?.first?.isLetter ?? false
} }
@objc var isOptionHotKey: Bool { @objc var isOptionHotKey: Bool {
flags.contains([.option]) && inputText?.first?.isLetter ?? false flags.contains([.option]) && inputText?.first?.isLetter ?? false
} }
@objc var isOptionHold: Bool { @objc var isOptionHold: Bool {
flags.contains([.option]) flags.contains([.option])
} }
@objc var isCapsLockOn: Bool { @objc var isCapsLockOn: Bool {
flags.contains([.capsLock]) flags.contains([.capsLock])
} }
@objc var isNumericPad: Bool { @objc var isNumericPad: Bool {
flags.contains([.numericPad]) flags.contains([.numericPad])
} }
@objc var isFunctionKeyHold: Bool { @objc var isFunctionKeyHold: Bool {
flags.contains([.function]) flags.contains([.function])
} }
@objc var isReservedKey: Bool { @objc var isReservedKey: Bool {
guard let code = KeyCode(rawValue: keyCode) else { guard let code = KeyCode(rawValue: keyCode) else {
return false return false
} }
return code.rawValue != KeyCode.none.rawValue return code.rawValue != KeyCode.none.rawValue
} }
@objc var isTab: Bool { @objc var isTab: Bool {
KeyCode(rawValue: keyCode) == KeyCode.tab KeyCode(rawValue: keyCode) == KeyCode.tab
} }
@objc var isEnter: Bool { @objc var isEnter: Bool {
(KeyCode(rawValue: keyCode) == KeyCode.enterCR) || (KeyCode(rawValue: keyCode) == KeyCode.enterLF) (KeyCode(rawValue: keyCode) == KeyCode.enterCR)
} || (KeyCode(rawValue: keyCode) == KeyCode.enterLF)
}
@objc var isUp: Bool { @objc var isUp: Bool {
KeyCode(rawValue: keyCode) == KeyCode.up KeyCode(rawValue: keyCode) == KeyCode.up
} }
@objc var isDown: Bool { @objc var isDown: Bool {
KeyCode(rawValue: keyCode) == KeyCode.down KeyCode(rawValue: keyCode) == KeyCode.down
} }
@objc var isLeft: Bool { @objc var isLeft: Bool {
KeyCode(rawValue: keyCode) == KeyCode.left KeyCode(rawValue: keyCode) == KeyCode.left
} }
@objc var isRight: Bool { @objc var isRight: Bool {
KeyCode(rawValue: keyCode) == KeyCode.right KeyCode(rawValue: keyCode) == KeyCode.right
} }
@objc var isPageUp: Bool { @objc var isPageUp: Bool {
KeyCode(rawValue: keyCode) == KeyCode.pageUp KeyCode(rawValue: keyCode) == KeyCode.pageUp
} }
@objc var isPageDown: Bool { @objc var isPageDown: Bool {
KeyCode(rawValue: keyCode) == KeyCode.pageDown KeyCode(rawValue: keyCode) == KeyCode.pageDown
} }
@objc var isSpace: Bool { @objc var isSpace: Bool {
KeyCode(rawValue: keyCode) == KeyCode.space KeyCode(rawValue: keyCode) == KeyCode.space
} }
@objc var isBackSpace: Bool { @objc var isBackSpace: Bool {
KeyCode(rawValue: keyCode) == KeyCode.backSpace KeyCode(rawValue: keyCode) == KeyCode.backSpace
} }
@objc var isESC: Bool { @objc var isESC: Bool {
KeyCode(rawValue: keyCode) == KeyCode.esc KeyCode(rawValue: keyCode) == KeyCode.esc
} }
@objc var isHome: Bool { @objc var isHome: Bool {
KeyCode(rawValue: keyCode) == KeyCode.home KeyCode(rawValue: keyCode) == KeyCode.home
} }
@objc var isEnd: Bool { @objc var isEnd: Bool {
KeyCode(rawValue: keyCode) == KeyCode.end KeyCode(rawValue: keyCode) == KeyCode.end
} }
@objc var isDelete: Bool { @objc var isDelete: Bool {
KeyCode(rawValue: keyCode) == KeyCode.delete KeyCode(rawValue: keyCode) == KeyCode.delete
} }
@objc var isCursorBackward: Bool { @objc var isCursorBackward: Bool {
KeyCode(rawValue: keyCode) == cursorBackwardKey KeyCode(rawValue: keyCode) == cursorBackwardKey
} }
@objc var isCursorForward: Bool { @objc var isCursorForward: Bool {
KeyCode(rawValue: keyCode) == cursorForwardKey KeyCode(rawValue: keyCode) == cursorForwardKey
} }
@objc var isAbsorbedArrowKey: Bool { @objc var isAbsorbedArrowKey: Bool {
KeyCode(rawValue: keyCode) == absorbedArrowKey KeyCode(rawValue: keyCode) == absorbedArrowKey
} }
@objc var isExtraChooseCandidateKey: Bool { @objc var isExtraChooseCandidateKey: Bool {
KeyCode(rawValue: keyCode) == extraChooseCandidateKey KeyCode(rawValue: keyCode) == extraChooseCandidateKey
} }
@objc var isExtraChooseCandidateKeyReverse: Bool { @objc var isExtraChooseCandidateKeyReverse: Bool {
KeyCode(rawValue: keyCode) == extraChooseCandidateKeyReverse KeyCode(rawValue: keyCode) == extraChooseCandidateKeyReverse
} }
@objc var isVerticalModeOnlyChooseCandidateKey: Bool { @objc var isVerticalModeOnlyChooseCandidateKey: Bool {
KeyCode(rawValue: keyCode) == verticalModeOnlyChooseCandidateKey KeyCode(rawValue: keyCode) == verticalModeOnlyChooseCandidateKey
} }
@objc var isUpperCaseASCIILetterKey: Bool { @objc var isUpperCaseASCIILetterKey: Bool {
// flags == .shift Shift // flags == .shift Shift
self.charCode >= 65 && self.charCode <= 90 && flags == .shift self.charCode >= 65 && self.charCode <= 90 && flags == .shift
} }
@objc var isSymbolMenuPhysicalKey: Bool { @objc var isSymbolMenuPhysicalKey: Bool {
// KeyCode macOS Apple // KeyCode macOS Apple
// ![input isShift] 使 Shift // ![input isShift] 使 Shift
KeyCode(rawValue: keyCode) == KeyCode.symbolMenuPhysicalKey KeyCode(rawValue: keyCode) == KeyCode.symbolMenuPhysicalKey
} }
} }
@objc enum vChewingEmacsKey: UInt16 { @objc enum vChewingEmacsKey: UInt16 {
case none = 0 case none = 0
case forward = 6 // F case forward = 6 // F
case backward = 2 // B case backward = 2 // B
case home = 1 // A case home = 1 // A
case end = 5 // E case end = 5 // E
case delete = 4 // D case delete = 4 // D
case nextPage = 22 // V case nextPage = 22 // V
} }
class EmacsKeyHelper: NSObject { class EmacsKeyHelper: NSObject {
@objc static func detect(charCode: UniChar, flags: NSEvent.ModifierFlags) -> vChewingEmacsKey { @objc static func detect(charCode: UniChar, flags: NSEvent.ModifierFlags) -> vChewingEmacsKey {
let charCode = AppleKeyboardConverter.cnvApple2ABC(charCode) let charCode = AppleKeyboardConverter.cnvApple2ABC(charCode)
if flags.contains(.control) { if flags.contains(.control) {
return vChewingEmacsKey(rawValue: charCode) ?? .none return vChewingEmacsKey(rawValue: charCode) ?? .none
} }
return .none; return .none
} }
} }

View File

@ -1,55 +1,67 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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" #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 new_line = [](char c) { return c == '\n' || c == '\r'; };
static auto blank = [](char c) { return c == ' ' || c == '\t'; }; static auto blank = [](char c) { return c == ' ' || c == '\t'; };
static auto blank_or_newline static auto blank_or_newline = [](char c) { return blank(c) || new_line(c); };
= [](char c) { return blank(c) || new_line(c); };
static auto content_char = [](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_; return state_;
} }
const char* key_begin = nullptr; const char *key_begin = nullptr;
size_t key_length = 0; size_t key_length = 0;
const char* value_begin = nullptr; const char *value_begin = nullptr;
size_t value_length = 0; size_t value_length = 0;
while (true) { while (true)
{
state_ = SkipUntilNot(blank_or_newline); state_ = SkipUntilNot(blank_or_newline);
if (state_ != State::CAN_CONTINUE) { if (state_ != State::CAN_CONTINUE)
{
return state_; return state_;
} }
// Check if it's a comment line; if so, read until end of line. // Check if it's a comment line; if so, read until end of line.
if (*current_ != '#') { if (*current_ != '#')
{
break; break;
} }
state_ = SkipUntil(new_line); state_ = SkipUntil(new_line);
if (state_ != State::CAN_CONTINUE) { if (state_ != State::CAN_CONTINUE)
{
return state_; return state_;
} }
} }
@ -59,22 +71,26 @@ KeyValueBlobReader::State KeyValueBlobReader::Next(KeyValue* out)
key_begin = current_; key_begin = current_;
state_ = SkipUntilNot(content_char); state_ = SkipUntilNot(content_char);
if (state_ != State::CAN_CONTINUE) { if (state_ != State::CAN_CONTINUE)
{
goto error; goto error;
} }
key_length = current_ - key_begin; key_length = current_ - key_begin;
// There should be at least one blank character after the key string. // There should be at least one blank character after the key string.
if (!blank(*current_)) { if (!blank(*current_))
{
goto error; goto error;
} }
state_ = SkipUntilNot(blank); state_ = SkipUntilNot(blank);
if (state_ != State::CAN_CONTINUE) { if (state_ != State::CAN_CONTINUE)
{
goto error; goto error;
} }
if (!content_char(*current_)) { if (!content_char(*current_))
{
goto error; 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. // like "foo bar baz\n" where baz should not be treated as the Next key.
SkipUntil(new_line); SkipUntil(new_line);
if (out != nullptr) { if (out != nullptr)
*out = KeyValue { std::string_view { key_begin, key_length }, {
std::string_view { value_begin, value_length } }; *out = KeyValue{std::string_view{key_begin, key_length}, std::string_view{value_begin, value_length}};
} }
state_ = State::HAS_PAIR; state_ = State::HAS_PAIR;
return state_; return state_;
@ -102,11 +118,12 @@ error:
return state_; return state_;
} }
KeyValueBlobReader::State KeyValueBlobReader::SkipUntilNot( KeyValueBlobReader::State KeyValueBlobReader::SkipUntilNot(const std::function<bool(char)> &f)
const std::function<bool(char)>& f)
{ {
while (current_ != end_ && *current_) { while (current_ != end_ && *current_)
if (!f(*current_)) { {
if (!f(*current_))
{
return State::CAN_CONTINUE; return State::CAN_CONTINUE;
} }
++current_; ++current_;
@ -115,11 +132,12 @@ KeyValueBlobReader::State KeyValueBlobReader::SkipUntilNot(
return State::END; return State::END;
} }
KeyValueBlobReader::State KeyValueBlobReader::SkipUntil( KeyValueBlobReader::State KeyValueBlobReader::SkipUntil(const std::function<bool(char)> &f)
const std::function<bool(char)>& f)
{ {
while (current_ != end_ && *current_) { while (current_ != end_ && *current_)
if (f(*current_)) { {
if (f(*current_))
{
return State::CAN_CONTINUE; return State::CAN_CONTINUE;
} }
++current_; ++current_;
@ -128,8 +146,7 @@ KeyValueBlobReader::State KeyValueBlobReader::SkipUntil(
return State::END; return State::END;
} }
std::ostream& operator<<( std::ostream &operator<<(std::ostream &os, const KeyValueBlobReader::KeyValue &kv)
std::ostream& os, const KeyValueBlobReader::KeyValue& kv)
{ {
os << "(key: " << kv.key << ", value: " << kv.value << ")"; os << "(key: " << kv.key << ", value: " << kv.value << ")";
return os; return os;

View File

@ -1,20 +1,27 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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_ #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, // 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 // 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. // example, read-only mmap can be used to parse large files.
namespace vChewing { namespace vChewing
{
class KeyValueBlobReader { class KeyValueBlobReader
public: {
enum class State : int { public:
enum class State : int
{
// There are no more key-value pairs in this blob. // There are no more key-value pairs in this blob.
END = 0, END = 0,
// The reader has produced a new key-value pair. // The reader has produced a new key-value pair.
@ -54,19 +64,16 @@ public:
CAN_CONTINUE = 2 CAN_CONTINUE = 2
}; };
struct KeyValue { struct KeyValue
constexpr KeyValue() {
: key("") constexpr KeyValue() : key(""), value("")
, value("")
{ {
} }
constexpr KeyValue(std::string_view k, std::string_view v) constexpr KeyValue(std::string_view k, std::string_view v) : key(k), value(v)
: key(k)
, value(v)
{ {
} }
bool operator==(const KeyValue& another) const bool operator==(const KeyValue &another) const
{ {
return key == another.key && value == another.value; return key == another.key && value == another.value;
} }
@ -75,27 +82,25 @@ public:
std::string_view value; std::string_view value;
}; };
KeyValueBlobReader(const char* blob, size_t size) KeyValueBlobReader(const char *blob, size_t size) : current_(blob), end_(blob + size)
: current_(blob)
, end_(blob + size)
{ {
} }
// Parse the next key-value pair and return the state of the reader. If // 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 // `out` is passed, out will be set to the produced key-value pair if there
// is one. // is one.
State Next(KeyValue* out = nullptr); State Next(KeyValue *out = nullptr);
private: private:
State SkipUntil(const std::function<bool(char)>& f); State SkipUntil(const std::function<bool(char)> &f);
State SkipUntilNot(const std::function<bool(char)>& f); State SkipUntilNot(const std::function<bool(char)> &f);
const char* current_; const char *current_;
const char* end_; const char *end_;
State state_ = State::CAN_CONTINUE; State state_ = State::CAN_CONTINUE;
}; };
std::ostream& operator<<(std::ostream&, const KeyValueBlobReader::KeyValue&); std::ostream &operator<<(std::ostream &, const KeyValueBlobReader::KeyValue &);
} // namespace vChewing } // namespace vChewing

View File

@ -1,69 +1,76 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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 Cocoa
public extension NSString { extension NSString {
/// Converts the index in an NSString to the index in a Swift 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 /// 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 /// 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 /// causes that the NSString and Swift string representation of the same
/// string have different lengths once the string contains such Emoji. The /// 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 /// method helps to find the index in a Swift string by passing the index
/// in an NSString. /// in an NSString.
func characterIndex(from utf16Index:Int) -> (Int, String) { public func characterIndex(from utf16Index: Int) -> (Int, String) {
let string = (self as String) let string = (self as String)
var length = 0 var length = 0
for (i, character) in string.enumerated() { for (i, character) in string.enumerated() {
length += character.utf16.count length += character.utf16.count
if length > utf16Index { if length > utf16Index {
return (i, string) return (i, string)
} }
} }
return (string.count, string) return (string.count, string)
} }
@objc func nextUtf16Position(for index: Int) -> Int { @objc public func nextUtf16Position(for index: Int) -> Int {
var (fixedIndex, string) = characterIndex(from: index) var (fixedIndex, string) = characterIndex(from: index)
if fixedIndex < string.count { if fixedIndex < string.count {
fixedIndex += 1 fixedIndex += 1
} }
return string[..<string.index(string.startIndex, offsetBy: fixedIndex)].utf16.count return string[..<string.index(string.startIndex, offsetBy: fixedIndex)].utf16.count
} }
@objc func previousUtf16Position(for index: Int) -> Int { @objc public func previousUtf16Position(for index: Int) -> Int {
var (fixedIndex, string) = characterIndex(from: index) var (fixedIndex, string) = characterIndex(from: index)
if fixedIndex > 0 { if fixedIndex > 0 {
fixedIndex -= 1 fixedIndex -= 1
} }
return string[..<string.index(string.startIndex, offsetBy: fixedIndex)].utf16.count return string[..<string.index(string.startIndex, offsetBy: fixedIndex)].utf16.count
} }
@objc var count: Int { @objc public var count: Int {
(self as String).count (self as String).count
} }
@objc func split() -> [NSString] { @objc public func split() -> [NSString] {
Array(self as String).map { Array(self as String).map {
NSString(string: String($0)) NSString(string: String($0))
} }
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,92 +1,104 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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 Cocoa
public protocol FSEventStreamHelperDelegate: AnyObject { 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 { public struct Event {
var path: String var path: String
var flags: FSEventStreamEventFlags var flags: FSEventStreamEventFlags
var id: FSEventStreamEventId var id: FSEventStreamEventId
} }
public let path: String public let path: String
public let dispatchQueue: DispatchQueue public let dispatchQueue: DispatchQueue
public weak var delegate: FSEventStreamHelperDelegate? public weak var delegate: FSEventStreamHelperDelegate?
@objc public init(path: String, queue: DispatchQueue) { @objc public init(path: String, queue: DispatchQueue) {
self.path = path self.path = path
self.dispatchQueue = queue self.dispatchQueue = queue
} }
private var stream: FSEventStreamRef? = nil private var stream: FSEventStreamRef? = nil
public func start() -> Bool { public func start() -> Bool {
if stream != nil { if stream != nil {
return false return false
} }
var context = FSEventStreamContext() var context = FSEventStreamContext()
context.info = Unmanaged.passUnretained(self).toOpaque() context.info = Unmanaged.passUnretained(self).toOpaque()
guard let stream = FSEventStreamCreate(nil, { guard
(stream, clientCallBackInfo, eventCount, eventPaths, eventFlags, eventIds) in let stream = FSEventStreamCreate(
let helper = Unmanaged<FSEventStreamHelper>.fromOpaque(clientCallBackInfo!).takeUnretainedValue() nil,
let pathsBase = eventPaths.assumingMemoryBound(to: UnsafePointer<CChar>.self) {
let pathsPtr = UnsafeBufferPointer(start: pathsBase, count: eventCount) (stream, clientCallBackInfo, eventCount, eventPaths, eventFlags, eventIds) in
let flagsPtr = UnsafeBufferPointer(start: eventFlags, count: eventCount) let helper = Unmanaged<FSEventStreamHelper>.fromOpaque(clientCallBackInfo!)
let eventIDsPtr = UnsafeBufferPointer(start: eventIds, count: eventCount) .takeUnretainedValue()
let events = (0..<eventCount).map { let pathsBase = eventPaths.assumingMemoryBound(to: UnsafePointer<CChar>.self)
FSEventStreamHelper.Event(path: String(cString: pathsPtr[$0]), let pathsPtr = UnsafeBufferPointer(start: pathsBase, count: eventCount)
flags: flagsPtr[$0], let flagsPtr = UnsafeBufferPointer(start: eventFlags, count: eventCount)
id: eventIDsPtr[$0] ) let eventIDsPtr = UnsafeBufferPointer(start: eventIds, count: eventCount)
} let events = (0..<eventCount).map {
helper.delegate?.helper(helper, didReceive: events) FSEventStreamHelper.Event(
}, path: String(cString: pathsPtr[$0]),
&context, flags: flagsPtr[$0],
[path] as CFArray, id: eventIDsPtr[$0])
UInt64(kFSEventStreamEventIdSinceNow), }
1.0, helper.delegate?.helper(helper, didReceive: events)
FSEventStreamCreateFlags(kFSEventStreamCreateFlagNone) },
) else { &context,
return false [path] as CFArray,
} UInt64(kFSEventStreamEventIdSinceNow),
1.0,
FSEventStreamCreateFlags(kFSEventStreamCreateFlagNone)
)
else {
return false
}
FSEventStreamSetDispatchQueue(stream, dispatchQueue) FSEventStreamSetDispatchQueue(stream, dispatchQueue)
if !FSEventStreamStart(stream) { if !FSEventStreamStart(stream) {
FSEventStreamInvalidate(stream) FSEventStreamInvalidate(stream)
return false return false
} }
self.stream = stream self.stream = stream
return true return true
} }
func stop() { func stop() {
guard let stream = stream else { guard let stream = stream else {
return return
} }
FSEventStreamStop(stream) FSEventStreamStop(stream)
FSEventStreamInvalidate(stream) FSEventStreamInvalidate(stream)
self.stream = nil self.stream = nil
} }
} }

View File

@ -1,40 +1,47 @@
// Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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 #ifndef LMConsolidator_hpp
#define LMConsolidator_hpp #define LMConsolidator_hpp
#include <syslog.h>
#include <stdio.h>
#include <fstream> #include <fstream>
#include <sstream>
#include <iostream> #include <iostream>
#include <string>
#include <map> #include <map>
#include <set>
#include <regex> #include <regex>
#include <set>
#include <sstream>
#include <stdio.h>
#include <string>
#include <syslog.h>
using namespace std; using namespace std;
namespace vChewing { namespace vChewing
{
class LMConsolidator class LMConsolidator
{ {
public: public:
static bool CheckPragma(const char *path); static bool CheckPragma(const char *path);
static bool FixEOF(const char *path); static bool FixEOF(const char *path);
static bool ConsolidateContent(const char *path, bool shouldCheckPragma); static bool ConsolidateContent(const char *path, bool shouldCheckPragma);

View File

@ -1,28 +1,35 @@
// Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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 "LMConsolidator.h"
#include "vChewing-Swift.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 // HEADER VERIFIER. CREDIT: Shiki Suen
bool LMConsolidator::CheckPragma(const char *path) bool LMConsolidator::CheckPragma(const char *path)
@ -32,13 +39,17 @@ bool LMConsolidator::CheckPragma(const char *path)
{ {
string firstLine; string firstLine;
getline(zfdCheckPragma, firstLine); getline(zfdCheckPragma, firstLine);
if (mgrPrefs.isDebugModeEnabled) syslog(LOG_CONS, "HEADER SEEN ||%s", firstLine.c_str()); if (mgrPrefs.isDebugModeEnabled)
if (firstLine != FORMATTED_PRAGMA_HEADER) { syslog(LOG_CONS, "HEADER SEEN ||%s", firstLine.c_str());
if (mgrPrefs.isDebugModeEnabled) syslog(LOG_CONS, "HEADER VERIFICATION FAILED. START IN-PLACE CONSOLIDATING PROCESS."); if (firstLine != FORMATTED_PRAGMA_HEADER)
{
if (mgrPrefs.isDebugModeEnabled)
syslog(LOG_CONS, "HEADER VERIFICATION FAILED. START IN-PLACE CONSOLIDATING PROCESS.");
return false; return false;
} }
} }
if (mgrPrefs.isDebugModeEnabled) syslog(LOG_CONS, "HEADER VERIFICATION SUCCESSFUL."); if (mgrPrefs.isDebugModeEnabled)
syslog(LOG_CONS, "HEADER VERIFICATION SUCCESSFUL.");
return true; return true;
} }
@ -46,58 +57,76 @@ bool LMConsolidator::CheckPragma(const char *path)
bool LMConsolidator::FixEOF(const char *path) bool LMConsolidator::FixEOF(const char *path)
{ {
std::fstream zfdEOFFixerIncomingStream(path); std::fstream zfdEOFFixerIncomingStream(path);
zfdEOFFixerIncomingStream.seekg(-1,std::ios_base::end); zfdEOFFixerIncomingStream.seekg(-1, std::ios_base::end);
char z; char z;
zfdEOFFixerIncomingStream.get(z); zfdEOFFixerIncomingStream.get(z);
if(z!='\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)
if (mgrPrefs.isDebugModeEnabled) syslog(LOG_CONS, "// PROCEDURE: Trying to insert a new line as EOF before per-line check process.\n"); 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); std::ofstream zfdEOFFixerOutput(path, std::ios_base::app);
zfdEOFFixerOutput << std::endl; zfdEOFFixerOutput << std::endl;
zfdEOFFixerOutput.close(); zfdEOFFixerOutput.close();
if (zfdEOFFixerOutput.fail()) { 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 (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; return false;
} }
} }
zfdEOFFixerIncomingStream.close(); zfdEOFFixerIncomingStream.close();
if (zfdEOFFixerIncomingStream.fail()) { 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 (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 false;
} }
return true; return true;
} // END: EOF FIXER. } // END: EOF FIXER.
// CONTENT CONSOLIDATOR. CREDIT: Shiki Suen. // 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); bool pragmaCheckResult = LMConsolidator::CheckPragma(path);
if (pragmaCheckResult && shouldCheckPragma){ if (pragmaCheckResult && shouldCheckPragma)
{
return true; return true;
} }
ifstream zfdContentConsolidatorIncomingStream(path); ifstream zfdContentConsolidatorIncomingStream(path);
vector<string>vecEntry; vector<string> vecEntry;
while(!zfdContentConsolidatorIncomingStream.eof()) while (!zfdContentConsolidatorIncomingStream.eof())
{ // Xcode 13 能用的 ObjCpp 與 Cpp 並無原生支援「\h」這個 Regex 參數的能力,只能逐行處理。 { // Xcode 13 能用的 ObjCpp 與 Cpp 並無原生支援「\h」這個 Regex 參數的能力,只能逐行處理。
string zfdBuffer; string zfdBuffer;
getline(zfdContentConsolidatorIncomingStream,zfdBuffer); getline(zfdContentConsolidatorIncomingStream, zfdBuffer);
vecEntry.push_back(zfdBuffer); vecEntry.push_back(zfdBuffer);
} }
// 第一遍 for 用來統整每行內的內容。 // 第一遍 for 用來統整每行內的內容。
// regex sedCJKWhiteSpace("\\x{3000}"), sedNonBreakWhiteSpace("\\x{A0}"), sedWhiteSpace("\\s+"), sedLeadingSpace("^\\s"), sedTrailingSpace("\\s$"); // 這樣寫會導致輸入法敲不了任何字,推測 Xcode 13 支援的 cpp / objCpp 可能對某些 Regex 寫法有相容性問題。 // regex sedCJKWhiteSpace("\\x{3000}"), sedNonBreakWhiteSpace("\\x{A0}"), sedWhiteSpace("\\s+"),
// regex sedCJKWhiteSpace(" "), sedNonBreakWhiteSpace(" "), sedWhiteSpace("\\s+"), sedLeadingSpace("^\\s"), sedTrailingSpace("\\s$"); // RegEx 先定義好。 // 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$)"); regex sedToConsolidate("( +| +| +|\t+)+"), sedToTrim("(^\\s|\\s$)");
for(int i=0;i<vecEntry.size();i++) { // 第一遍 for 用來統整每行內的內容。 for (int i = 0; i < vecEntry.size(); i++)
if (vecEntry[i].size() != 0) { // 不要理會空行,否則給空行加上 endl 等於再加空行。 { // 第一遍 for 用來統整每行內的內容。
// RegEx 處理順序:先將全形空格換成西文空格,然後合併任何意義上的連續空格(包括 tab 等),最後去除每行首尾空格。 if (vecEntry[i].size() != 0)
// vecEntry[i] = regex_replace(vecEntry[i], sedCJKWhiteSpace, " ").c_str(); // 中日韓全形空格轉為 ASCII 空格。 { // 不要理會空行,否則給空行加上 endl 等於再加空行。
// vecEntry[i] = regex_replace(vecEntry[i], sedNonBreakWhiteSpace, " ").c_str(); // Non-Break 型空格轉為 ASCII 空格。 // RegEx 處理順序:先將全形空格換成西文空格,然後合併任何意義上的連續空格(包括 tab
// vecEntry[i] = regex_replace(vecEntry[i], sedWhiteSpace, " ").c_str(); // 所有意義上的連續的 \s 型空格都轉為單個 ASCII 空格。 // 等),最後去除每行首尾空格。 vecEntry[i] = regex_replace(vecEntry[i], sedCJKWhiteSpace, " ").c_str(); //
// vecEntry[i] = regex_replace(vecEntry[i], sedLeadingSpace, "").c_str(); // 去掉行首空格。 // 中日韓全形空格轉為 ASCII 空格。 vecEntry[i] = regex_replace(vecEntry[i], sedNonBreakWhiteSpace, "
// vecEntry[i] = regex_replace(vecEntry[i], sedTrailingSpace, "").c_str(); // 去掉行尾空格。 // ").c_str(); // Non-Break 型空格轉為 ASCII 空格。 vecEntry[i] = regex_replace(vecEntry[i], sedWhiteSpace,
// " ").c_str(); // 所有意義上的連續的 \s 型空格都轉為單個 ASCII 空格。 vecEntry[i] =
// regex_replace(vecEntry[i], sedLeadingSpace, "").c_str(); // 去掉行首空格。 vecEntry[i] =
// regex_replace(vecEntry[i], sedTrailingSpace, "").c_str(); // 去掉行尾空格。
// 上述命令分步驟執行容易產生效能問題,故濃縮為下述兩句。 // 上述命令分步驟執行容易產生效能問題,故濃縮為下述兩句。
vecEntry[i] = regex_replace(vecEntry[i], sedToConsolidate, " ").c_str(); vecEntry[i] = regex_replace(vecEntry[i], sedToConsolidate, " ").c_str();
vecEntry[i] = regex_replace(vecEntry[i], sedToTrim, "").c_str(); vecEntry[i] = regex_replace(vecEntry[i], sedToTrim, "").c_str();
@ -106,27 +135,39 @@ bool LMConsolidator::ConsolidateContent(const char *path, bool shouldCheckPragma
// 在第二遍 for 運算之前,針對 vecEntry 去除重複條目。 // 在第二遍 for 運算之前,針對 vecEntry 去除重複條目。
std::reverse(vecEntry.begin(), vecEntry.end()); // 先首尾顛倒,免得破壞最新的 override 資訊。 std::reverse(vecEntry.begin(), vecEntry.end()); // 先首尾顛倒,免得破壞最新的 override 資訊。
vecEntry.erase(unique(vecEntry.begin(), vecEntry.end()), vecEntry.end()); // 去重複。 vecEntry.erase(unique(vecEntry.begin(), vecEntry.end()), vecEntry.end()); // 去重複。
std::reverse(vecEntry.begin(), vecEntry.end()); // 再顛倒回來。 std::reverse(vecEntry.begin(), vecEntry.end()); // 再顛倒回來。
// 統整完畢。開始將統整過的內容寫入檔案。 // 統整完畢。開始將統整過的內容寫入檔案。
ofstream zfdContentConsolidatorOutput(path); // 這裡是要從頭開始重寫檔案內容,所以不需要「 ios_base::app 」。 ofstream zfdContentConsolidatorOutput(path); // 這裡是要從頭開始重寫檔案內容,所以不需要「 ios_base::app 」。
if (!pragmaCheckResult){ if (!pragmaCheckResult)
zfdContentConsolidatorOutput<<FORMATTED_PRAGMA_HEADER<<endl; // 寫入經過整理處理的 HEADER。 {
zfdContentConsolidatorOutput << FORMATTED_PRAGMA_HEADER << endl; // 寫入經過整理處理的 HEADER。
} }
for(int i=0;i<vecEntry.size();i++) { // 第二遍 for 用來寫入統整過的內容。 for (int i = 0; i < vecEntry.size(); i++)
if (vecEntry[i].size() != 0) { // 這句很重要,不然還是會把經過 RegEx 處理後出現的空行搞到檔案裡。 { // 第二遍 for 用來寫入統整過的內容。
zfdContentConsolidatorOutput<<vecEntry[i]<<endl; // 這裡是必須得加上 endl 的,不然所有行都變成一個整合行。 if (vecEntry[i].size() != 0)
{ // 這句很重要,不然還是會把經過 RegEx 處理後出現的空行搞到檔案裡。
zfdContentConsolidatorOutput << vecEntry[i]
<< endl; // 這裡是必須得加上 endl 的,不然所有行都變成一個整合行。
} }
} }
zfdContentConsolidatorOutput.close(); zfdContentConsolidatorOutput.close();
if (zfdContentConsolidatorOutput.fail()) { if (zfdContentConsolidatorOutput.fail())
if (mgrPrefs.isDebugModeEnabled) syslog(LOG_CONS, "// REPORT: Failed to write content-consolidated data to the file. Insufficient Privileges?\n"); {
if (mgrPrefs.isDebugModeEnabled) syslog(LOG_CONS, "// DATA FILE: %s", path); if (mgrPrefs.isDebugModeEnabled)
syslog(LOG_CONS,
"// REPORT: Failed to write content-consolidated data to the file. Insufficient Privileges?\n");
if (mgrPrefs.isDebugModeEnabled)
syslog(LOG_CONS, "// DATA FILE: %s", path);
return false; return false;
} }
zfdContentConsolidatorIncomingStream.close(); zfdContentConsolidatorIncomingStream.close();
if (zfdContentConsolidatorIncomingStream.fail()) { if (zfdContentConsolidatorIncomingStream.fail())
if (mgrPrefs.isDebugModeEnabled) syslog(LOG_CONS, "// REPORT: Failed to read lines through the data file for content-consolidation. Insufficient Privileges?\n"); {
if (mgrPrefs.isDebugModeEnabled) syslog(LOG_CONS, "// DATA FILE: %s", path); if (mgrPrefs.isDebugModeEnabled)
syslog(LOG_CONS, "// REPORT: Failed to read lines through the data file for content-consolidation. "
"Insufficient Privileges?\n");
if (mgrPrefs.isDebugModeEnabled)
syslog(LOG_CONS, "// DATA FILE: %s", path);
return false; return false;
} }
return true; return true;

View File

@ -1,168 +1,227 @@
// Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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 Cocoa
@objc public class IME: NSObject { @objc public class IME: NSObject {
static let dlgOpenPath = NSOpenPanel(); static let dlgOpenPath = NSOpenPanel()
// MARK: - Print debug information to the console. // MARK: -
@objc static func prtDebugIntel(_ strPrint: String) { @objc static var areWeUsingOurOwnPhraseEditor: Bool = false
if mgrPrefs.isDebugModeEnabled {
NSLog("vChewingErrorCallback: %@", strPrint)
}
}
// MARK: - Tell whether this IME is running with Root privileges. // MARK: - ctlInputMethod
@objc static var isSudoMode: Bool { static func getInputMode() -> InputMode {
NSUserName() == "root" return ctlInputMethod.currentKeyHandler.inputMode
} }
// MARK: - Initializing Language Models. // MARK: - Print debug information to the console.
@objc static func initLangModels(userOnly: Bool) { @objc static func prtDebugIntel(_ strPrint: String) {
if !userOnly { if mgrPrefs.isDebugModeEnabled {
mgrLangModel.loadDataModels() // NSLog("vChewingErrorCallback: %@", strPrint)
} }
// mgrLangModel loadUserPhrases dataFolderPath }
//
//
mgrLangModel.loadUserPhrases()
mgrLangModel.loadUserPhraseReplacement()
mgrLangModel.loadUserAssociatedPhrases()
}
// MARK: - System Dark Mode Status Detector. // MARK: - Tell whether this IME is running with Root privileges.
@objc static func isDarkMode() -> Bool { @objc static var isSudoMode: Bool {
if #available(macOS 10.15, *) { NSUserName() == "root"
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. // MARK: - Initializing Language Models.
@discardableResult static func trashTargetIfExists(_ path: String) -> Bool { @objc static func initLangModels(userOnly: Bool) {
do { if !userOnly {
if FileManager.default.fileExists(atPath: path) { mgrLangModel.loadDataModels() //
// }
try FileManager.default.trashItem(at: URL(fileURLWithPath: path), resultingItemURL: nil) // mgrLangModel loadUserPhrases dataFolderPath
} else { //
NSLog("Item doesn't exist: \(path)") //
} mgrLangModel.loadUserPhrases()
} catch let error as NSError { mgrLangModel.loadUserPhraseReplacement()
NSLog("Failed from removing this object: \(path) || Error: \(error)") mgrLangModel.loadUserAssociatedPhrases()
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
}
let kTargetBin = "vChewing" // MARK: - System Dark Mode Status Detector.
let kTargetBundle = "/vChewing.app" @objc static func isDarkMode() -> Bool {
let pathLibrary = isSudo ? "/Library" : FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask)[0].path if #available(macOS 10.15, *) {
let pathIMELibrary = isSudo ? "/Library/Input Methods" : FileManager.default.urls(for: .inputMethodsDirectory, in: .userDomainMask)[0].path let appearanceDescription = NSApplication.shared.effectiveAppearance.debugDescription
let pathUnitKeyboardLayouts = "/Keyboard Layouts" .lowercased()
let arrKeyLayoutFiles = ["/vChewing ETen.keylayout", "/vChewingKeyLayout.bundle", "/vChewing MiTAC.keylayout", "/vChewing IBM.keylayout", "/vChewing FakeSeigyou.keylayout", "/vChewing Dachen.keylayout"] 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: - Open a phrase data file.
for objPath in arrKeyLayoutFiles { static func openPhraseFile(userFileAt path: String) {
let objFullPath = pathLibrary + pathUnitKeyboardLayouts + objPath func checkIfUserFilesExist() -> Bool {
if !IME.trashTargetIfExists(objFullPath) { return -1 } if !mgrLangModel.checkIfUserLanguageModelFilesExist() {
} let content = String(
if CommandLine.arguments.count > 2 && CommandLine.arguments[2] == "--all" && CommandLine.arguments[1] == "uninstall" { format: NSLocalizedString(
// 使 "Please check the permission at \"%@\".", comment: ""),
// 使 symbol link mgrLangModel.dataFolderPath(isDefaultFolder: false))
// symbol link ctlNonModalAlertWindow.shared.show(
IME.trashTargetIfExists(mgrLangModel.dataFolderPath(isDefaultFolder: true)) title: NSLocalizedString("Unable to create the user phrase file.", comment: ""),
IME.trashTargetIfExists(pathLibrary + "/Preferences/" + bundleID + ".plist") // App content: content, confirmButtonTitle: NSLocalizedString("OK", comment: ""),
} cancelButtonTitle: nil, cancelAsDefault: false, delegate: nil)
if !IME.trashTargetIfExists(pathIMELibrary + kTargetBundle) { return -1 } // App NSApp.setActivationPolicy(.accessory)
// return false
if selfKill { }
let killTask = Process() return true
killTask.launchPath = "/usr/bin/killall" }
killTask.arguments = ["-9", kTargetBin]
killTask.launch()
killTask.waitUntilExit()
}
return 0
}
// MARK: - Registering the input method. if !checkIfUserFilesExist() {
@discardableResult static func registerInputMethod() -> Int32 { return
guard let bundleID = Bundle.main.bundleIdentifier else { }
return -1 NSWorkspace.shared.openFile(path, withApplication: "vChewingPhraseEditor")
} }
let bundleUrl = Bundle.main.bundleURL
var maybeInputSource = InputSourceHelper.inputSource(for: bundleID)
if maybeInputSource == nil { // MARK: - Trash a file if it exists.
NSLog("Registering input source \(bundleID) at \(bundleUrl.absoluteString)"); @discardableResult static func trashTargetIfExists(_ path: String) -> Bool {
// then register do {
let status = InputSourceHelper.registerTnputSource(at: bundleUrl) 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
}
if !status { // MARK: - Uninstall the input method.
NSLog("Fatal error: Cannot register input source \(bundleID) at \(bundleUrl.absoluteString).") @discardableResult static func uninstall(isSudo: Bool = false, selfKill: Bool = true) -> Int32 {
return -1 // Bundle.main.bundleURL便使 sudo
} guard let bundleID = Bundle.main.bundleIdentifier else {
NSLog("Failed to ensure the bundle identifier.")
return -1
}
maybeInputSource = InputSourceHelper.inputSource(for: bundleID) 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",
]
guard let inputSource = maybeInputSource else { //
NSLog("Fatal error: Cannot find input source \(bundleID) after registration.") for objPath in arrKeyLayoutFiles {
return -1 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 !InputSourceHelper.inputSourceEnabled(for: inputSource) { // MARK: - Registering the input method.
NSLog("Enabling input source \(bundleID) at \(bundleUrl.absoluteString).") @discardableResult static func registerInputMethod() -> Int32 {
let status = InputSourceHelper.enable(inputSource: inputSource) guard let bundleID = Bundle.main.bundleIdentifier else {
if !status { return -1
NSLog("Fatal error: Cannot enable input source \(bundleID).") }
return -1 let bundleUrl = Bundle.main.bundleURL
} var maybeInputSource = InputSourceHelper.inputSource(for: bundleID)
if !InputSourceHelper.inputSourceEnabled(for: inputSource) {
NSLog("Fatal error: Cannot enable input source \(bundleID).")
return -1
}
}
if CommandLine.arguments.count > 2 && CommandLine.arguments[2] == "--all" { if maybeInputSource == nil {
let enabled = InputSourceHelper.enableAllInputMode(for: bundleID) NSLog("Registering input source \(bundleID) at \(bundleUrl.absoluteString)")
NSLog(enabled ? "All input sources enabled for \(bundleID)" : "Cannot enable all input sources for \(bundleID), but this is ignored") // then register
} let status = InputSourceHelper.registerTnputSource(at: bundleUrl)
return 0
}
if !status {
NSLog(
"Fatal error: Cannot register input source \(bundleID) at \(bundleUrl.absoluteString)."
)
return -1
}
maybeInputSource = InputSourceHelper.inputSource(for: bundleID)
}
guard let inputSource = maybeInputSource else {
NSLog("Fatal error: Cannot find input source \(bundleID) after registration.")
return -1
}
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
}
} }

View File

@ -1,127 +1,140 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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 Carbon
import Cocoa
public class InputSourceHelper: NSObject { public class InputSourceHelper: NSObject {
@available(*, unavailable) @available(*, unavailable)
public override init() { public override init() {
super.init() super.init()
} }
public static func allInstalledInputSources() -> [TISInputSource] { public static func allInstalledInputSources() -> [TISInputSource] {
TISCreateInputSourceList(nil, true).takeRetainedValue() as! [TISInputSource] TISCreateInputSourceList(nil, true).takeRetainedValue() as! [TISInputSource]
} }
@objc(inputSourceForProperty:stringValue:) @objc(inputSourceForProperty:stringValue:)
public static func inputSource(for propertyKey: CFString, stringValue: String) -> TISInputSource? { public static func inputSource(for propertyKey: CFString, stringValue: String)
let stringID = CFStringGetTypeID() -> TISInputSource?
for source in allInstalledInputSources() { {
if let propertyPtr = TISGetInputSourceProperty(source, propertyKey) { let stringID = CFStringGetTypeID()
let property = Unmanaged<CFTypeRef>.fromOpaque(propertyPtr).takeUnretainedValue() for source in allInstalledInputSources() {
let typeID = CFGetTypeID(property) if let propertyPtr = TISGetInputSourceProperty(source, propertyKey) {
if typeID != stringID { let property = Unmanaged<CFTypeRef>.fromOpaque(propertyPtr).takeUnretainedValue()
continue let typeID = CFGetTypeID(property)
} if typeID != stringID {
if stringValue == property as? String { continue
return source }
} if stringValue == property as? String {
} return source
} }
return nil }
} }
return nil
}
@objc(inputSourceForInputSourceID:) @objc(inputSourceForInputSourceID:)
public static func inputSource(for sourceID: String) -> TISInputSource? { public static func inputSource(for sourceID: String) -> TISInputSource? {
inputSource(for: kTISPropertyInputSourceID, stringValue: sourceID) inputSource(for: kTISPropertyInputSourceID, stringValue: sourceID)
} }
@objc(inputSourceEnabled:) @objc(inputSourceEnabled:)
public static func inputSourceEnabled(for source: TISInputSource) -> Bool { public static func inputSourceEnabled(for source: TISInputSource) -> Bool {
if let valuePts = TISGetInputSourceProperty(source, kTISPropertyInputSourceIsEnabled) { if let valuePts = TISGetInputSourceProperty(source, kTISPropertyInputSourceIsEnabled) {
let value = Unmanaged<CFBoolean>.fromOpaque(valuePts).takeUnretainedValue() let value = Unmanaged<CFBoolean>.fromOpaque(valuePts).takeUnretainedValue()
return value == kCFBooleanTrue return value == kCFBooleanTrue
} }
return false return false
} }
@objc(enableInputSource:) @objc(enableInputSource:)
public static func enable(inputSource: TISInputSource) -> Bool { public static func enable(inputSource: TISInputSource) -> Bool {
let status = TISEnableInputSource(inputSource) let status = TISEnableInputSource(inputSource)
return status == noErr return status == noErr
} }
@objc(enableAllInputModesForInputSourceBundleID:) @objc(enableAllInputModesForInputSourceBundleID:)
public static func enableAllInputMode(for inputSourceBundleD: String) -> Bool { public static func enableAllInputMode(for inputSourceBundleD: String) -> Bool {
var enabled = false var enabled = false
for source in allInstalledInputSources() { for source in allInstalledInputSources() {
guard let bundleIDPtr = TISGetInputSourceProperty(source, kTISPropertyBundleID), guard let bundleIDPtr = TISGetInputSourceProperty(source, kTISPropertyBundleID),
let _ = TISGetInputSourceProperty(source, kTISPropertyInputModeID) else { let _ = TISGetInputSourceProperty(source, kTISPropertyInputModeID)
continue else {
} continue
let bundleID = Unmanaged<CFString>.fromOpaque(bundleIDPtr).takeUnretainedValue() }
if String(bundleID) == inputSourceBundleD { let bundleID = Unmanaged<CFString>.fromOpaque(bundleIDPtr).takeUnretainedValue()
let modeEnabled = self.enable(inputSource: source) if String(bundleID) == inputSourceBundleD {
if !modeEnabled { let modeEnabled = self.enable(inputSource: source)
return false if !modeEnabled {
} return false
enabled = true }
} enabled = true
} }
}
return enabled return enabled
} }
@objc(enableInputMode:forInputSourceBundleID:) @objc(enableInputMode:forInputSourceBundleID:)
public static func enable(inputMode modeID: String, for bundleID: String) -> Bool { public static func enable(inputMode modeID: String, for bundleID: String) -> Bool {
for source in allInstalledInputSources() { for source in allInstalledInputSources() {
guard let bundleIDPtr = TISGetInputSourceProperty(source, kTISPropertyBundleID), guard let bundleIDPtr = TISGetInputSourceProperty(source, kTISPropertyBundleID),
let modePtr = TISGetInputSourceProperty(source, kTISPropertyInputModeID) else { let modePtr = TISGetInputSourceProperty(source, kTISPropertyInputModeID)
continue else {
} continue
let inputsSourceBundleID = Unmanaged<CFString>.fromOpaque(bundleIDPtr).takeUnretainedValue() }
let inputsSourceModeID = Unmanaged<CFString>.fromOpaque(modePtr).takeUnretainedValue() let inputsSourceBundleID = Unmanaged<CFString>.fromOpaque(bundleIDPtr)
if modeID == String(inputsSourceModeID) && bundleID == String(inputsSourceBundleID) { .takeUnretainedValue()
let enabled = enable(inputSource: source) let inputsSourceModeID = Unmanaged<CFString>.fromOpaque(modePtr).takeUnretainedValue()
print("Attempt to enable input source of mode: \(modeID), bundle ID: \(bundleID), result: \(enabled)") if modeID == String(inputsSourceModeID) && bundleID == String(inputsSourceBundleID) {
return enabled 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)") print("Failed to find any matching input source of mode: \(modeID), bundle ID: \(bundleID)")
return false return false
} }
@objc(disableInputSource:) @objc(disableInputSource:)
public static func disable(inputSource: TISInputSource) -> Bool { public static func disable(inputSource: TISInputSource) -> Bool {
let status = TISDisableInputSource(inputSource) let status = TISDisableInputSource(inputSource)
return status == noErr return status == noErr
} }
@objc(registerInputSource:) @objc(registerInputSource:)
public static func registerTnputSource(at url: URL) -> Bool { public static func registerTnputSource(at url: URL) -> Bool {
let status = TISRegisterInputSource(url as CFURL) let status = TISRegisterInputSource(url as CFURL)
return status == noErr return status == noErr
} }
} }

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -1,20 +1,27 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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 Cocoa
@ -72,464 +79,492 @@ private let kDefaultKeys = "123456789"
@propertyWrapper @propertyWrapper
struct UserDefault<Value> { struct UserDefault<Value> {
let key: String let key: String
let defaultValue: Value let defaultValue: Value
var container: UserDefaults = .standard var container: UserDefaults = .standard
var wrappedValue: Value { var wrappedValue: Value {
get { get {
container.object(forKey: key) as? Value ?? defaultValue container.object(forKey: key) as? Value ?? defaultValue
} }
set { set {
container.set(newValue, forKey: key) container.set(newValue, forKey: key)
} }
} }
} }
@propertyWrapper @propertyWrapper
struct CandidateListTextSize { struct CandidateListTextSize {
let key: String let key: String
let defaultValue: CGFloat = kDefaultCandidateListTextSize let defaultValue: CGFloat = kDefaultCandidateListTextSize
lazy var container: UserDefault = { lazy var container: UserDefault = {
UserDefault(key: key, defaultValue: defaultValue) UserDefault(key: key, defaultValue: defaultValue)
}() }()
var wrappedValue: CGFloat { var wrappedValue: CGFloat {
mutating get { mutating get {
var value = container.wrappedValue var value = container.wrappedValue
if value < kMinCandidateListTextSize { if value < kMinCandidateListTextSize {
value = kMinCandidateListTextSize value = kMinCandidateListTextSize
} else if value > kMaxCandidateListTextSize { } else if value > kMaxCandidateListTextSize {
value = kMaxCandidateListTextSize value = kMaxCandidateListTextSize
} }
return value return value
} }
set { set {
var value = newValue var value = newValue
if value < kMinCandidateListTextSize { if value < kMinCandidateListTextSize {
value = kMinCandidateListTextSize value = kMinCandidateListTextSize
} else if value > kMaxCandidateListTextSize { } else if value > kMaxCandidateListTextSize {
value = kMaxCandidateListTextSize value = kMaxCandidateListTextSize
} }
container.wrappedValue = value container.wrappedValue = value
} }
} }
} }
@propertyWrapper @propertyWrapper
struct ComposingBufferSize { struct ComposingBufferSize {
let key: String let key: String
let defaultValue: Int = kDefaultComposingBufferSize let defaultValue: Int = kDefaultComposingBufferSize
lazy var container: UserDefault = { lazy var container: UserDefault = {
UserDefault(key: key, defaultValue: defaultValue) UserDefault(key: key, defaultValue: defaultValue)
}() }()
var wrappedValue: Int { var wrappedValue: Int {
mutating get { mutating get {
let currentValue = container.wrappedValue let currentValue = container.wrappedValue
if currentValue < kMinComposingBufferSize { if currentValue < kMinComposingBufferSize {
return kMinComposingBufferSize return kMinComposingBufferSize
} else if currentValue > kMaxComposingBufferSize { } else if currentValue > kMaxComposingBufferSize {
return kMaxComposingBufferSize return kMaxComposingBufferSize
} }
return currentValue return currentValue
} }
set { set {
var value = newValue var value = newValue
if value < kMinComposingBufferSize { if value < kMinComposingBufferSize {
value = kMinComposingBufferSize value = kMinComposingBufferSize
} else if value > kMaxComposingBufferSize { } else if value > kMaxComposingBufferSize {
value = kMaxComposingBufferSize value = kMaxComposingBufferSize
} }
container.wrappedValue = value container.wrappedValue = value
} }
} }
} }
// MARK: - // MARK: -
@objc enum KeyboardLayout: Int { @objc enum KeyboardLayout: Int {
case standard = 0 case ofStandard = 0
case eten = 1 case ofEten = 1
case hsu = 2 case ofHsu = 2
case eten26 = 3 case ofEen26 = 3
case IBM = 4 case ofIBM = 4
case MiTAC = 5 case ofMiTAC = 5
case FakeSeigyou = 6 case ofFakeSeigyou = 6
case hanyuPinyin = 10 case ofHanyuPinyin = 10
var name: String { var name: String {
switch (self) { switch self {
case .standard: case .ofStandard:
return "Standard" return "Standard"
case .eten: case .ofEten:
return "ETen" return "ETen"
case .hsu: case .ofHsu:
return "Hsu" return "Hsu"
case .eten26: case .ofEen26:
return "ETen26" return "ETen26"
case .IBM: case .ofIBM:
return "IBM" return "IBM"
case .MiTAC: case .ofMiTAC:
return "MiTAC" return "MiTAC"
case .FakeSeigyou: case .ofFakeSeigyou:
return "FakeSeigyou" return "FakeSeigyou"
case .hanyuPinyin: case .ofHanyuPinyin:
return "HanyuPinyin" return "HanyuPinyin"
} }
} }
} }
// MARK: - // MARK: -
@objc public class mgrPrefs: NSObject { @objc public class mgrPrefs: NSObject {
static var allKeys:[String] { static var allKeys: [String] {
[kIsDebugModeEnabled, [
kUserDataFolderSpecified, kIsDebugModeEnabled,
kKeyboardLayoutPreference, kUserDataFolderSpecified,
kBasisKeyboardLayoutPreference, kKeyboardLayoutPreference,
kShowPageButtonsInCandidateWindow, kBasisKeyboardLayoutPreference,
kCandidateListTextSize, kShowPageButtonsInCandidateWindow,
kAppleLanguagesPreferences, kCandidateListTextSize,
kShouldAutoReloadUserDataFiles, kAppleLanguagesPreferences,
kSelectPhraseAfterCursorAsCandidatePreference, kShouldAutoReloadUserDataFiles,
kUseHorizontalCandidateListPreference, kSelectPhraseAfterCursorAsCandidatePreference,
kComposingBufferSizePreference, kUseHorizontalCandidateListPreference,
kChooseCandidateUsingSpace, kComposingBufferSizePreference,
kCNS11643Enabled, kChooseCandidateUsingSpace,
kSymbolInputEnabled, kCNS11643Enabled,
kChineseConversionEnabled, kSymbolInputEnabled,
kShiftJISShinjitaiOutputEnabled, kChineseConversionEnabled,
kHalfWidthPunctuationEnabled, kShiftJISShinjitaiOutputEnabled,
kSpecifyTabKeyBehavior, kHalfWidthPunctuationEnabled,
kSpecifySpaceKeyBehavior, kSpecifyTabKeyBehavior,
kEscToCleanInputBuffer, kSpecifySpaceKeyBehavior,
kCandidateTextFontName, kEscToCleanInputBuffer,
kCandidateKeyLabelFontName, kCandidateTextFontName,
kCandidateKeys, kCandidateKeyLabelFontName,
kMoveCursorAfterSelectingCandidate, kCandidateKeys,
kPhraseReplacementEnabled, kMoveCursorAfterSelectingCandidate,
kUseSCPCTypingMode, kPhraseReplacementEnabled,
kMaxCandidateLength, kUseSCPCTypingMode,
kShouldNotFartInLieuOfBeep, kMaxCandidateLength,
kAssociatedPhrasesEnabled] kShouldNotFartInLieuOfBeep,
} kAssociatedPhrasesEnabled,
]
@objc public static func setMissingDefaults () { }
// Preferences Module plist private
@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: 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: 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: 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: kSymbolInputEnabled) == nil {
if UserDefaults.standard.object(forKey: kCandidateListTextSize) == nil { UserDefaults.standard.set(mgrPrefs.symbolInputEnabled, forKey: kSymbolInputEnabled)
UserDefaults.standard.set(mgrPrefs.candidateListTextSize, forKey: kCandidateListTextSize) }
}
// 18
// true if UserDefaults.standard.object(forKey: kCandidateListTextSize) == nil {
if UserDefaults.standard.object(forKey: kChooseCandidateUsingSpace) == nil { UserDefaults.standard.set(
UserDefaults.standard.set(mgrPrefs.chooseCandidateUsingSpace, forKey: kChooseCandidateUsingSpace) mgrPrefs.candidateListTextSize, forKey: kCandidateListTextSize)
} }
// 使 // true
if UserDefaults.standard.object(forKey: kShouldAutoReloadUserDataFiles) == nil { if UserDefaults.standard.object(forKey: kChooseCandidateUsingSpace) == nil {
UserDefaults.standard.set(mgrPrefs.shouldAutoReloadUserDataFiles, forKey: kShouldAutoReloadUserDataFiles) UserDefaults.standard.set(
} mgrPrefs.chooseCandidateUsingSpace, forKey: kChooseCandidateUsingSpace)
}
// Tab
if UserDefaults.standard.object(forKey: kSpecifyTabKeyBehavior) == nil { // 使
UserDefaults.standard.set(mgrPrefs.specifyTabKeyBehavior, forKey: kSpecifyTabKeyBehavior) if UserDefaults.standard.object(forKey: kShouldAutoReloadUserDataFiles) == nil {
} UserDefaults.standard.set(
mgrPrefs.shouldAutoReloadUserDataFiles, forKey: kShouldAutoReloadUserDataFiles)
// Space }
if UserDefaults.standard.object(forKey: kSpecifySpaceKeyBehavior) == nil {
UserDefaults.standard.set(mgrPrefs.specifySpaceKeyBehavior, forKey: kSpecifySpaceKeyBehavior) // Tab
} if UserDefaults.standard.object(forKey: kSpecifyTabKeyBehavior) == nil {
UserDefaults.standard.set(
// false mgrPrefs.specifyTabKeyBehavior, forKey: kSpecifyTabKeyBehavior)
if UserDefaults.standard.object(forKey: kUseSCPCTypingMode) == nil { }
UserDefaults.standard.set(mgrPrefs.useSCPCTypingMode, forKey: kUseSCPCTypingMode)
} // Space
if UserDefaults.standard.object(forKey: kSpecifySpaceKeyBehavior) == nil {
// false UserDefaults.standard.set(
if UserDefaults.standard.object(forKey: kAssociatedPhrasesEnabled) == nil { mgrPrefs.specifySpaceKeyBehavior, forKey: kSpecifySpaceKeyBehavior)
UserDefaults.standard.set(mgrPrefs.associatedPhrasesEnabled, forKey: kAssociatedPhrasesEnabled) }
}
// false
// 0 if UserDefaults.standard.object(forKey: kUseSCPCTypingMode) == nil {
if UserDefaults.standard.object(forKey: kSelectPhraseAfterCursorAsCandidatePreference) == nil { UserDefaults.standard.set(mgrPrefs.useSCPCTypingMode, forKey: kUseSCPCTypingMode)
UserDefaults.standard.set(mgrPrefs.selectPhraseAfterCursorAsCandidate, forKey: kSelectPhraseAfterCursorAsCandidatePreference) }
}
// false
// if UserDefaults.standard.object(forKey: kAssociatedPhrasesEnabled) == nil {
if UserDefaults.standard.object(forKey: kMoveCursorAfterSelectingCandidate) == nil { UserDefaults.standard.set(
UserDefaults.standard.set(mgrPrefs.moveCursorAfterSelectingCandidate, forKey: kMoveCursorAfterSelectingCandidate) mgrPrefs.associatedPhrasesEnabled, forKey: kAssociatedPhrasesEnabled)
} }
// // 0
if UserDefaults.standard.object(forKey: kUseHorizontalCandidateListPreference) == nil { if UserDefaults.standard.object(forKey: kSelectPhraseAfterCursorAsCandidatePreference)
UserDefaults.standard.set(mgrPrefs.useHorizontalCandidateList, forKey: kUseHorizontalCandidateListPreference) == nil
} {
UserDefaults.standard.set(
// mgrPrefs.selectPhraseAfterCursorAsCandidate,
if UserDefaults.standard.object(forKey: kCNS11643Enabled) == nil { forKey: kSelectPhraseAfterCursorAsCandidatePreference)
UserDefaults.standard.set(mgrPrefs.cns11643Enabled, forKey: kCNS11643Enabled) }
}
//
// if UserDefaults.standard.object(forKey: kMoveCursorAfterSelectingCandidate) == nil {
if UserDefaults.standard.object(forKey: kChineseConversionEnabled) == nil { UserDefaults.standard.set(
UserDefaults.standard.set(mgrPrefs.chineseConversionEnabled, forKey: kChineseConversionEnabled) mgrPrefs.moveCursorAfterSelectingCandidate,
} forKey: kMoveCursorAfterSelectingCandidate)
}
// JIS
if UserDefaults.standard.object(forKey: kShiftJISShinjitaiOutputEnabled) == nil { //
UserDefaults.standard.set(mgrPrefs.shiftJISShinjitaiOutputEnabled, forKey: kShiftJISShinjitaiOutputEnabled) if UserDefaults.standard.object(forKey: kUseHorizontalCandidateListPreference) == nil {
} UserDefaults.standard.set(
mgrPrefs.useHorizontalCandidateList, forKey: kUseHorizontalCandidateListPreference)
// }
if UserDefaults.standard.object(forKey: kPhraseReplacementEnabled) == nil {
UserDefaults.standard.set(mgrPrefs.phraseReplacementEnabled, forKey: kPhraseReplacementEnabled) //
} if UserDefaults.standard.object(forKey: kCNS11643Enabled) == nil {
UserDefaults.standard.set(mgrPrefs.cns11643Enabled, forKey: kCNS11643Enabled)
// }
if UserDefaults.standard.object(forKey: kShouldNotFartInLieuOfBeep) == nil {
UserDefaults.standard.set(mgrPrefs.shouldNotFartInLieuOfBeep, forKey: kShouldNotFartInLieuOfBeep) //
} if UserDefaults.standard.object(forKey: kChineseConversionEnabled) == nil {
UserDefaults.standard.set(
UserDefaults.standard.synchronize() mgrPrefs.chineseConversionEnabled, forKey: kChineseConversionEnabled)
} }
@UserDefault(key: kIsDebugModeEnabled, defaultValue: false) // JIS
@objc static var isDebugModeEnabled: Bool if UserDefaults.standard.object(forKey: kShiftJISShinjitaiOutputEnabled) == nil {
UserDefaults.standard.set(
@UserDefault(key: kUserDataFolderSpecified, defaultValue: "") mgrPrefs.shiftJISShinjitaiOutputEnabled, forKey: kShiftJISShinjitaiOutputEnabled)
@objc static var userDataFolderSpecified: String }
@objc static func ifSpecifiedUserDataPathExistsInPlist() -> Bool { //
UserDefaults.standard.object(forKey: kUserDataFolderSpecified) != nil if UserDefaults.standard.object(forKey: kPhraseReplacementEnabled) == nil {
} UserDefaults.standard.set(
mgrPrefs.phraseReplacementEnabled, forKey: kPhraseReplacementEnabled)
@UserDefault(key: kAppleLanguagesPreferences, defaultValue: []) }
@objc static var appleLanguages: Array<String>
//
@UserDefault(key: kKeyboardLayoutPreference, defaultValue: 0) if UserDefaults.standard.object(forKey: kShouldNotFartInLieuOfBeep) == nil {
@objc static var keyboardLayout: Int UserDefaults.standard.set(
mgrPrefs.shouldNotFartInLieuOfBeep, forKey: kShouldNotFartInLieuOfBeep)
@objc static var keyboardLayoutName: String { }
(KeyboardLayout(rawValue: self.keyboardLayout) ?? KeyboardLayout.standard).name
} UserDefaults.standard.synchronize()
}
@UserDefault(key: kBasisKeyboardLayoutPreference, defaultValue: "com.apple.keylayout.ZhuyinBopomofo")
@objc static var basisKeyboardLayout: String @UserDefault(key: kIsDebugModeEnabled, defaultValue: false)
@objc static var isDebugModeEnabled: Bool
@UserDefault(key: kShowPageButtonsInCandidateWindow, defaultValue: true)
@objc static var showPageButtonsInCandidateWindow: Bool @UserDefault(key: kUserDataFolderSpecified, defaultValue: "")
@objc static var userDataFolderSpecified: String
@CandidateListTextSize(key: kCandidateListTextSize)
@objc static var candidateListTextSize: CGFloat @objc static func ifSpecifiedUserDataPathExistsInPlist() -> Bool {
UserDefaults.standard.object(forKey: kUserDataFolderSpecified) != nil
@UserDefault(key: kShouldAutoReloadUserDataFiles, defaultValue: true) }
@objc static var shouldAutoReloadUserDataFiles: Bool
@UserDefault(key: kAppleLanguagesPreferences, defaultValue: [])
@UserDefault(key: kSelectPhraseAfterCursorAsCandidatePreference, defaultValue: false) @objc static var appleLanguages: [String]
@objc static var selectPhraseAfterCursorAsCandidate: Bool
@UserDefault(key: kKeyboardLayoutPreference, defaultValue: 0)
@UserDefault(key: kMoveCursorAfterSelectingCandidate, defaultValue: false) @objc static var keyboardLayout: Int
@objc static var moveCursorAfterSelectingCandidate: Bool
@objc static var keyboardLayoutName: String {
@UserDefault(key: kUseHorizontalCandidateListPreference, defaultValue: true) (KeyboardLayout(rawValue: self.keyboardLayout) ?? KeyboardLayout.ofStandard).name
@objc static var useHorizontalCandidateList: Bool }
@ComposingBufferSize(key: kComposingBufferSizePreference) @UserDefault(
@objc static var composingBufferSize: Int key: kBasisKeyboardLayoutPreference, defaultValue: "com.apple.keylayout.ZhuyinBopomofo")
@objc static var basisKeyboardLayout: String
@UserDefault(key: kChooseCandidateUsingSpace, defaultValue: true)
@objc static var chooseCandidateUsingSpace: Bool @UserDefault(key: kShowPageButtonsInCandidateWindow, defaultValue: true)
@objc static var showPageButtonsInCandidateWindow: Bool
@UserDefault(key: kUseSCPCTypingMode, defaultValue: false)
@objc static var useSCPCTypingMode: Bool @CandidateListTextSize(key: kCandidateListTextSize)
@objc static var candidateListTextSize: CGFloat
@objc static func toggleSCPCTypingModeEnabled() -> Bool {
useSCPCTypingMode = !useSCPCTypingMode @UserDefault(key: kShouldAutoReloadUserDataFiles, defaultValue: true)
UserDefaults.standard.set(useSCPCTypingMode, forKey: kUseSCPCTypingMode) @objc static var shouldAutoReloadUserDataFiles: Bool
return useSCPCTypingMode
} @UserDefault(key: kSelectPhraseAfterCursorAsCandidatePreference, defaultValue: false)
@objc static var selectPhraseAfterCursorAsCandidate: Bool
@UserDefault(key: kMaxCandidateLength, defaultValue: kDefaultComposingBufferSize * 2)
@objc static var maxCandidateLength: Int @UserDefault(key: kMoveCursorAfterSelectingCandidate, defaultValue: false)
@objc static var moveCursorAfterSelectingCandidate: Bool
@UserDefault(key: kShouldNotFartInLieuOfBeep, defaultValue: true)
@objc static var shouldNotFartInLieuOfBeep: Bool @UserDefault(key: kUseHorizontalCandidateListPreference, defaultValue: true)
@objc static var useHorizontalCandidateList: Bool
@objc static func toggleShouldNotFartInLieuOfBeep() -> Bool {
shouldNotFartInLieuOfBeep = !shouldNotFartInLieuOfBeep @ComposingBufferSize(key: kComposingBufferSizePreference)
UserDefaults.standard.set(shouldNotFartInLieuOfBeep, forKey: kShouldNotFartInLieuOfBeep) @objc static var composingBufferSize: Int
return shouldNotFartInLieuOfBeep
} @UserDefault(key: kChooseCandidateUsingSpace, defaultValue: true)
@objc static var chooseCandidateUsingSpace: Bool
@UserDefault(key: kCNS11643Enabled, defaultValue: false)
@objc static var cns11643Enabled: Bool @UserDefault(key: kUseSCPCTypingMode, defaultValue: false)
@objc static var useSCPCTypingMode: Bool
@objc static func toggleCNS11643Enabled() -> Bool {
cns11643Enabled = !cns11643Enabled @objc static func toggleSCPCTypingModeEnabled() -> Bool {
mgrLangModel.setCNSEnabled(cns11643Enabled) // useSCPCTypingMode = !useSCPCTypingMode
UserDefaults.standard.set(cns11643Enabled, forKey: kCNS11643Enabled) UserDefaults.standard.set(useSCPCTypingMode, forKey: kUseSCPCTypingMode)
return cns11643Enabled return useSCPCTypingMode
} }
@UserDefault(key: kSymbolInputEnabled, defaultValue: true) @UserDefault(key: kMaxCandidateLength, defaultValue: kDefaultComposingBufferSize * 2)
@objc static var symbolInputEnabled: Bool @objc static var maxCandidateLength: Int
@objc static func toggleSymbolInputEnabled() -> Bool { @UserDefault(key: kShouldNotFartInLieuOfBeep, defaultValue: true)
symbolInputEnabled = !symbolInputEnabled @objc static var shouldNotFartInLieuOfBeep: Bool
mgrLangModel.setSymbolEnabled(symbolInputEnabled) //
UserDefaults.standard.set(symbolInputEnabled, forKey: kSymbolInputEnabled) @objc static func toggleShouldNotFartInLieuOfBeep() -> Bool {
return symbolInputEnabled shouldNotFartInLieuOfBeep = !shouldNotFartInLieuOfBeep
} UserDefaults.standard.set(shouldNotFartInLieuOfBeep, forKey: kShouldNotFartInLieuOfBeep)
return shouldNotFartInLieuOfBeep
@UserDefault(key: kChineseConversionEnabled, defaultValue: false) }
@objc static var chineseConversionEnabled: Bool
@UserDefault(key: kCNS11643Enabled, defaultValue: false)
@objc @discardableResult static func toggleChineseConversionEnabled() -> Bool { @objc static var cns11643Enabled: Bool
chineseConversionEnabled = !chineseConversionEnabled
// JIS @objc static func toggleCNS11643Enabled() -> Bool {
if chineseConversionEnabled && shiftJISShinjitaiOutputEnabled { cns11643Enabled = !cns11643Enabled
self.toggleShiftJISShinjitaiOutputEnabled() mgrLangModel.setCNSEnabled(cns11643Enabled) //
UserDefaults.standard.set(shiftJISShinjitaiOutputEnabled, forKey: kShiftJISShinjitaiOutputEnabled) UserDefaults.standard.set(cns11643Enabled, forKey: kCNS11643Enabled)
} return cns11643Enabled
UserDefaults.standard.set(chineseConversionEnabled, forKey: kChineseConversionEnabled) }
return chineseConversionEnabled
} @UserDefault(key: kSymbolInputEnabled, defaultValue: true)
@objc static var symbolInputEnabled: Bool
@UserDefault(key: kShiftJISShinjitaiOutputEnabled, defaultValue: false)
@objc static var shiftJISShinjitaiOutputEnabled: Bool @objc static func toggleSymbolInputEnabled() -> Bool {
symbolInputEnabled = !symbolInputEnabled
@objc @discardableResult static func toggleShiftJISShinjitaiOutputEnabled() -> Bool { mgrLangModel.setSymbolEnabled(symbolInputEnabled) //
shiftJISShinjitaiOutputEnabled = !shiftJISShinjitaiOutputEnabled UserDefaults.standard.set(symbolInputEnabled, forKey: kSymbolInputEnabled)
// JIS return symbolInputEnabled
if shiftJISShinjitaiOutputEnabled && chineseConversionEnabled {self.toggleChineseConversionEnabled()} }
UserDefaults.standard.set(shiftJISShinjitaiOutputEnabled, forKey: kShiftJISShinjitaiOutputEnabled)
return shiftJISShinjitaiOutputEnabled @UserDefault(key: kChineseConversionEnabled, defaultValue: false)
} @objc static var chineseConversionEnabled: Bool
@UserDefault(key: kHalfWidthPunctuationEnabled, defaultValue: false) @objc @discardableResult static func toggleChineseConversionEnabled() -> Bool {
@objc static var halfWidthPunctuationEnabled: Bool chineseConversionEnabled = !chineseConversionEnabled
// JIS
@objc static func toggleHalfWidthPunctuationEnabled() -> Bool { if chineseConversionEnabled && shiftJISShinjitaiOutputEnabled {
halfWidthPunctuationEnabled = !halfWidthPunctuationEnabled self.toggleShiftJISShinjitaiOutputEnabled()
return halfWidthPunctuationEnabled UserDefaults.standard.set(
} shiftJISShinjitaiOutputEnabled, forKey: kShiftJISShinjitaiOutputEnabled)
}
@UserDefault(key: kEscToCleanInputBuffer, defaultValue: true) UserDefaults.standard.set(chineseConversionEnabled, forKey: kChineseConversionEnabled)
@objc static var escToCleanInputBuffer: Bool return chineseConversionEnabled
}
@UserDefault(key: kSpecifyTabKeyBehavior, defaultValue: false) @UserDefault(key: kShiftJISShinjitaiOutputEnabled, defaultValue: false)
@objc static var specifyTabKeyBehavior: Bool @objc static var shiftJISShinjitaiOutputEnabled: Bool
@UserDefault(key: kSpecifySpaceKeyBehavior, defaultValue: false) @objc @discardableResult static func toggleShiftJISShinjitaiOutputEnabled() -> Bool {
@objc static var specifySpaceKeyBehavior: Bool shiftJISShinjitaiOutputEnabled = !shiftJISShinjitaiOutputEnabled
// JIS
// MARK: - Optional settings if shiftJISShinjitaiOutputEnabled && chineseConversionEnabled {
@UserDefault(key: kCandidateTextFontName, defaultValue: nil) self.toggleChineseConversionEnabled()
@objc static var candidateTextFontName: String? }
UserDefaults.standard.set(
@UserDefault(key: kCandidateKeyLabelFontName, defaultValue: nil) shiftJISShinjitaiOutputEnabled, forKey: kShiftJISShinjitaiOutputEnabled)
@objc static var candidateKeyLabelFontName: String? return shiftJISShinjitaiOutputEnabled
}
@UserDefault(key: kCandidateKeys, defaultValue: kDefaultKeys)
@objc static var candidateKeys: String @UserDefault(key: kHalfWidthPunctuationEnabled, defaultValue: false)
@objc static var halfWidthPunctuationEnabled: Bool
@objc static var defaultCandidateKeys: String {
kDefaultKeys @objc static func toggleHalfWidthPunctuationEnabled() -> Bool {
} halfWidthPunctuationEnabled = !halfWidthPunctuationEnabled
@objc static var suggestedCandidateKeys: [String] { return halfWidthPunctuationEnabled
[kDefaultKeys, "234567890", "QWERTYUIO", "QWERTASDF", "ASDFGHJKL", "ASDFZXCVB"] }
}
@UserDefault(key: kEscToCleanInputBuffer, defaultValue: true)
@objc static func validate(candidateKeys: String) throws { @objc static var escToCleanInputBuffer: Bool
let trimmed = candidateKeys.trimmingCharacters(in: .whitespacesAndNewlines)
if trimmed.isEmpty { @UserDefault(key: kSpecifyTabKeyBehavior, defaultValue: false)
throw CandidateKeyError.empty @objc static var specifyTabKeyBehavior: Bool
}
if !trimmed.canBeConverted(to: .ascii) { @UserDefault(key: kSpecifySpaceKeyBehavior, defaultValue: false)
throw CandidateKeyError.invalidCharacters @objc static var specifySpaceKeyBehavior: Bool
}
if trimmed.contains(" ") { // MARK: - Optional settings
throw CandidateKeyError.containSpace @UserDefault(key: kCandidateTextFontName, defaultValue: nil)
} @objc static var candidateTextFontName: String?
if trimmed.count < 4 {
throw CandidateKeyError.tooShort @UserDefault(key: kCandidateKeyLabelFontName, defaultValue: nil)
} @objc static var candidateKeyLabelFontName: String?
if trimmed.count > 15 {
throw CandidateKeyError.tooLong @UserDefault(key: kCandidateKeys, defaultValue: kDefaultKeys)
} @objc static var candidateKeys: String
let set = Set(Array(trimmed))
if set.count != trimmed.count { @objc static var defaultCandidateKeys: String {
throw CandidateKeyError.duplicatedCharacters kDefaultKeys
} }
} @objc static var suggestedCandidateKeys: [String] {
[kDefaultKeys, "234567890", "QWERTYUIO", "QWERTASDF", "ASDFGHJKL", "ASDFZXCVB"]
enum CandidateKeyError: Error, LocalizedError { }
case empty
case invalidCharacters @objc static func validate(candidateKeys: String) throws {
case containSpace let trimmed = candidateKeys.trimmingCharacters(in: .whitespacesAndNewlines)
case duplicatedCharacters if trimmed.isEmpty {
case tooShort throw CandidateKeyError.empty
case tooLong }
if !trimmed.canBeConverted(to: .ascii) {
var errorDescription: String? { throw CandidateKeyError.invalidCharacters
switch self { }
case .empty: if trimmed.contains(" ") {
return NSLocalizedString("Candidates keys cannot be empty.", comment: "") throw CandidateKeyError.containSpace
case .invalidCharacters: }
return NSLocalizedString("Candidate keys can only contain ASCII characters like alphanumericals.", comment: "") if trimmed.count < 4 {
case .containSpace: throw CandidateKeyError.tooShort
return NSLocalizedString("Candidate keys cannot contain space.", comment: "") }
case .duplicatedCharacters: if trimmed.count > 15 {
return NSLocalizedString("There should not be duplicated keys.", comment: "") throw CandidateKeyError.tooLong
case .tooShort: }
return NSLocalizedString("Please specify at least 4 candidate keys.", comment: "") let set = Set(Array(trimmed))
case .tooLong: if set.count != trimmed.count {
return NSLocalizedString("Maximum 15 candidate keys allowed.", comment: "") throw CandidateKeyError.duplicatedCharacters
} }
} }
} enum CandidateKeyError: Error, LocalizedError {
case empty
@UserDefault(key: kPhraseReplacementEnabled, defaultValue: false) case invalidCharacters
@objc static var phraseReplacementEnabled: Bool case containSpace
case duplicatedCharacters
@objc static func togglePhraseReplacementEnabled() -> Bool { case tooShort
phraseReplacementEnabled = !phraseReplacementEnabled case tooLong
mgrLangModel.setPhraseReplacementEnabled(phraseReplacementEnabled)
UserDefaults.standard.set(phraseReplacementEnabled, forKey: kPhraseReplacementEnabled) var errorDescription: String? {
return phraseReplacementEnabled switch self {
} case .empty:
return NSLocalizedString("Candidates keys cannot be empty.", comment: "")
@UserDefault(key: kAssociatedPhrasesEnabled, defaultValue: false) case .invalidCharacters:
@objc static var associatedPhrasesEnabled: Bool return NSLocalizedString(
"Candidate keys can only contain ASCII characters like alphanumericals.",
@objc static func toggleAssociatedPhrasesEnabled() -> Bool { comment: "")
associatedPhrasesEnabled = !associatedPhrasesEnabled case .containSpace:
UserDefaults.standard.set(associatedPhrasesEnabled, forKey: kAssociatedPhrasesEnabled) return NSLocalizedString("Candidate keys cannot contain space.", comment: "")
return associatedPhrasesEnabled 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
}
} }

View File

@ -1,28 +1,35 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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 LMInstantiator_H #ifndef LMInstantiator_H
#define LMInstantiator_H #define LMInstantiator_H
#include "AssociatedPhrases.h" #include "AssociatedPhrases.h"
#include "CoreLM.h"
#include "CNSLM.h" #include "CNSLM.h"
#include "CoreLM.h"
#include "ParselessLM.h" #include "ParselessLM.h"
#include "PhraseReplacementMap.h" #include "PhraseReplacementMap.h"
#include "SymbolLM.h" #include "SymbolLM.h"
@ -31,7 +38,8 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR TH
#include <stdio.h> #include <stdio.h>
#include <unordered_set> #include <unordered_set>
namespace vChewing { namespace vChewing
{
using namespace Gramambular; using namespace Gramambular;
@ -57,58 +65,59 @@ using namespace Gramambular;
/// model while launching and to load the user phrases anytime if the custom /// 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 /// 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. /// you have to pass the paths when you ask it to do loading.
class LMInstantiator : public Gramambular::LanguageModel { class LMInstantiator : public Gramambular::LanguageModel
public: {
public:
LMInstantiator(); LMInstantiator();
~LMInstantiator(); ~LMInstantiator();
/// Asks to load the primary language model at the given path. /// Asks to load the primary language model at the given path.
/// @param languageModelPath The path of the language model. /// @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. /// If the data model is already loaded.
bool isDataModelLoaded(); bool isDataModelLoaded();
/// Asks to load the primary language model at the given path. /// Asks to load the primary language model at the given path.
/// @param miscDataPath The path of the misc data model. /// @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. /// If the data model is already loaded.
bool isMiscDataLoaded(); bool isMiscDataLoaded();
/// Asks to load the primary language model at the given path. /// Asks to load the primary language model at the given path.
/// @param symbolDataPath The path of the symbol data model. /// @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. /// If the data model is already loaded.
bool isSymbolDataLoaded(); bool isSymbolDataLoaded();
/// Asks to load the primary language model at the given path. /// Asks to load the primary language model at the given path.
/// @param cnsDataPath The path of the CNS data model. /// @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. /// If the data model is already loaded.
bool isCNSDataLoaded(); bool isCNSDataLoaded();
/// Asks to load the user phrases and excluded phrases at the given path. /// Asks to load the user phrases and excluded phrases at the given path.
/// @param userPhrasesPath The path of user phrases. /// @param userPhrasesPath The path of user phrases.
/// @param excludedPhrasesPath The path of excluded phrases. /// @param excludedPhrasesPath The path of excluded phrases.
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. /// Asks to load the user symbol data at the given path.
/// @param userSymbolDataPath The path of user symbol data. /// @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. /// Asks to load the user associated phrases at the given path.
/// @param userAssociatedPhrasesPath The path of the user associated phrases. /// @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. /// Asks to load the phrase replacement table at the given path.
/// @param phraseReplacementPath The path of the phrase replacement table. /// @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. /// Not implemented since we do not have data to provide bigram function.
const std::vector<Gramambular::Bigram> bigramsForKeys(const std::string& preceedingKey, const std::string& key); const std::vector<Gramambular::Bigram> bigramsForKeys(const std::string &preceedingKey, const std::string &key);
/// Returns a list of available unigram for the given 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 /// @param key A std::string represents the BPMF reading or a symbol key. For
/// example, it you pass "ㄇㄚ", it returns "嗎", "媽", and so on. /// example, it you pass "ㄇㄚ", it returns "嗎", "媽", and so on.
const std::vector<Gramambular::Unigram> unigramsForKey(const std::string& key); const std::vector<Gramambular::Unigram> unigramsForKey(const std::string &key);
/// If the model has unigrams for the given key. /// If the model has unigrams for the given key.
/// @param key The key. /// @param key The key.
bool hasUnigramsForKey(const std::string& key); bool hasUnigramsForKey(const std::string &key);
/// Enables or disables phrase replacement. /// Enables or disables phrase replacement.
void setPhraseReplacementEnabled(bool enabled); void setPhraseReplacementEnabled(bool enabled);
@ -125,21 +134,20 @@ public:
/// If CNS11643 input is enabled or not. /// If CNS11643 input is enabled or not.
bool cnsEnabled(); bool cnsEnabled();
const std::vector<std::string> associatedPhrasesForKey(const std::string& key); const std::vector<std::string> associatedPhrasesForKey(const std::string &key);
bool hasAssociatedPhrasesForKey(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. /// Filters and converts the input unigrams and return a new list of unigrams.
/// ///
/// @param unigrams The unigrams to be processed. /// @param unigrams The unigrams to be processed.
/// @param excludedValues The values to excluded unigrams. /// @param excludedValues The values to excluded unigrams.
/// @param insertedValues The values for unigrams already in the results. /// @param insertedValues The values for unigrams already in the results.
/// It helps to prevent duplicated unigrams. Please note that the method /// It helps to prevent duplicated unigrams. Please note that the method
/// has a side effect that it inserts values to `insertedValues`. /// has a side effect that it inserts values to `insertedValues`.
const std::vector<Gramambular::Unigram> filterAndTransformUnigrams(const std::vector<Gramambular::Unigram> unigrams, const std::vector<Gramambular::Unigram> filterAndTransformUnigrams(
const std::unordered_set<std::string>& excludedValues, const std::vector<Gramambular::Unigram> unigrams, const std::unordered_set<std::string> &excludedValues,
std::unordered_set<std::string>& insertedValues); std::unordered_set<std::string> &insertedValues);
ParselessLM m_languageModel; ParselessLM m_languageModel;
CoreLM m_miscModel; CoreLM m_miscModel;
@ -154,6 +162,6 @@ protected:
bool m_cnsEnabled; bool m_cnsEnabled;
bool m_symbolEnabled; bool m_symbolEnabled;
}; };
}; }; // namespace vChewing
#endif #endif

View File

@ -1,27 +1,35 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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 "LMInstantiator.h"
#include <algorithm> #include <algorithm>
#include <iterator> #include <iterator>
namespace vChewing { namespace vChewing
{
LMInstantiator::LMInstantiator() LMInstantiator::LMInstantiator()
{ {
@ -39,9 +47,10 @@ LMInstantiator::~LMInstantiator()
m_associatedPhrases.close(); m_associatedPhrases.close();
} }
void LMInstantiator::loadLanguageModel(const char* languageModelDataPath) void LMInstantiator::loadLanguageModel(const char *languageModelDataPath)
{ {
if (languageModelDataPath) { if (languageModelDataPath)
{
m_languageModel.close(); m_languageModel.close();
m_languageModel.open(languageModelDataPath); m_languageModel.open(languageModelDataPath);
} }
@ -52,9 +61,10 @@ bool LMInstantiator::isDataModelLoaded()
return m_languageModel.isLoaded(); 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.close();
m_cnsModel.open(cnsDataPath); m_cnsModel.open(cnsDataPath);
} }
@ -65,9 +75,10 @@ bool LMInstantiator::isCNSDataLoaded()
return m_cnsModel.isLoaded(); 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.close();
m_miscModel.open(miscDataPath); m_miscModel.open(miscDataPath);
} }
@ -78,9 +89,10 @@ bool LMInstantiator::isMiscDataLoaded()
return m_miscModel.isLoaded(); 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.close();
m_symbolModel.open(symbolDataPath); m_symbolModel.open(symbolDataPath);
} }
@ -91,14 +103,15 @@ bool LMInstantiator::isSymbolDataLoaded()
return m_symbolModel.isLoaded(); return m_symbolModel.isLoaded();
} }
void LMInstantiator::loadUserPhrases(const char* userPhrasesDataPath, void LMInstantiator::loadUserPhrases(const char *userPhrasesDataPath, const char *excludedPhrasesDataPath)
const char* excludedPhrasesDataPath)
{ {
if (userPhrasesDataPath) { if (userPhrasesDataPath)
{
m_userPhrases.close(); m_userPhrases.close();
m_userPhrases.open(userPhrasesDataPath); m_userPhrases.open(userPhrasesDataPath);
} }
if (excludedPhrasesDataPath) { if (excludedPhrasesDataPath)
{
m_excludedPhrases.close(); m_excludedPhrases.close();
m_excludedPhrases.open(excludedPhrasesDataPath); m_excludedPhrases.open(excludedPhrasesDataPath);
} }
@ -106,7 +119,8 @@ void LMInstantiator::loadUserPhrases(const char* userPhrasesDataPath,
void LMInstantiator::loadUserSymbolData(const char *userSymbolDataPath) void LMInstantiator::loadUserSymbolData(const char *userSymbolDataPath)
{ {
if (userSymbolDataPath) { if (userSymbolDataPath)
{
m_userSymbolModel.close(); m_userSymbolModel.close();
m_userSymbolModel.open(userSymbolDataPath); m_userSymbolModel.open(userSymbolDataPath);
} }
@ -114,28 +128,32 @@ void LMInstantiator::loadUserSymbolData(const char *userSymbolDataPath)
void LMInstantiator::loadUserAssociatedPhrases(const char *userAssociatedPhrasesPath) void LMInstantiator::loadUserAssociatedPhrases(const char *userAssociatedPhrasesPath)
{ {
if (userAssociatedPhrasesPath) { if (userAssociatedPhrasesPath)
{
m_associatedPhrases.close(); m_associatedPhrases.close();
m_associatedPhrases.open(userAssociatedPhrasesPath); 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.close();
m_phraseReplacement.open(phraseReplacementPath); m_phraseReplacement.open(phraseReplacementPath);
} }
} }
const std::vector<Gramambular::Bigram> LMInstantiator::bigramsForKeys(const std::string& preceedingKey, const std::string& key) const std::vector<Gramambular::Bigram> LMInstantiator::bigramsForKeys(const std::string &preceedingKey,
const std::string &key)
{ {
return std::vector<Gramambular::Bigram>(); return std::vector<Gramambular::Bigram>();
} }
const std::vector<Gramambular::Unigram> LMInstantiator::unigramsForKey(const std::string& key) const std::vector<Gramambular::Unigram> LMInstantiator::unigramsForKey(const std::string &key)
{ {
if (key == " ") { if (key == " ")
{
std::vector<Gramambular::Unigram> spaceUnigrams; std::vector<Gramambular::Unigram> spaceUnigrams;
Gramambular::Unigram g; Gramambular::Unigram g;
g.keyValue.key = " "; g.keyValue.key = " ";
@ -152,17 +170,18 @@ const std::vector<Gramambular::Unigram> LMInstantiator::unigramsForKey(const std
std::vector<Gramambular::Unigram> userSymbolUnigrams; std::vector<Gramambular::Unigram> userSymbolUnigrams;
std::vector<Gramambular::Unigram> cnsUnigrams; std::vector<Gramambular::Unigram> cnsUnigrams;
std::unordered_set<std::string> excludedValues; std::unordered_set<std::string> excludedValues;
std::unordered_set<std::string> insertedValues; std::unordered_set<std::string> insertedValues;
if (m_excludedPhrases.hasUnigramsForKey(key)) { if (m_excludedPhrases.hasUnigramsForKey(key))
{
std::vector<Gramambular::Unigram> excludedUnigrams = m_excludedPhrases.unigramsForKey(key); std::vector<Gramambular::Unigram> excludedUnigrams = m_excludedPhrases.unigramsForKey(key);
transform(excludedUnigrams.begin(), excludedUnigrams.end(), transform(excludedUnigrams.begin(), excludedUnigrams.end(), inserter(excludedValues, excludedValues.end()),
inserter(excludedValues, excludedValues.end()), [](const Gramambular::Unigram &u) { return u.keyValue.value; });
[](const Gramambular::Unigram& u) { return u.keyValue.value; });
} }
if (m_userPhrases.hasUnigramsForKey(key)) { if (m_userPhrases.hasUnigramsForKey(key))
{
std::vector<Gramambular::Unigram> rawUserUnigrams = m_userPhrases.unigramsForKey(key); std::vector<Gramambular::Unigram> rawUserUnigrams = m_userPhrases.unigramsForKey(key);
// 用這句指令讓使用者語彙檔案內的詞條優先順序隨著行數增加而逐漸增高。 // 用這句指令讓使用者語彙檔案內的詞條優先順序隨著行數增加而逐漸增高。
// 這樣一來就可以在就地新增語彙時徹底複寫優先權。 // 這樣一來就可以在就地新增語彙時徹底複寫優先權。
@ -170,27 +189,32 @@ const std::vector<Gramambular::Unigram> LMInstantiator::unigramsForKey(const std
userUnigrams = filterAndTransformUnigrams(rawUserUnigrams, excludedValues, insertedValues); userUnigrams = filterAndTransformUnigrams(rawUserUnigrams, excludedValues, insertedValues);
} }
if (m_languageModel.hasUnigramsForKey(key)) { if (m_languageModel.hasUnigramsForKey(key))
{
std::vector<Gramambular::Unigram> rawGlobalUnigrams = m_languageModel.unigramsForKey(key); std::vector<Gramambular::Unigram> rawGlobalUnigrams = m_languageModel.unigramsForKey(key);
allUnigrams = filterAndTransformUnigrams(rawGlobalUnigrams, excludedValues, insertedValues); allUnigrams = filterAndTransformUnigrams(rawGlobalUnigrams, excludedValues, insertedValues);
} }
if (m_miscModel.hasUnigramsForKey(key)) { if (m_miscModel.hasUnigramsForKey(key))
{
std::vector<Gramambular::Unigram> rawMiscUnigrams = m_miscModel.unigramsForKey(key); std::vector<Gramambular::Unigram> rawMiscUnigrams = m_miscModel.unigramsForKey(key);
miscUnigrams = filterAndTransformUnigrams(rawMiscUnigrams, excludedValues, insertedValues); miscUnigrams = filterAndTransformUnigrams(rawMiscUnigrams, excludedValues, insertedValues);
} }
if (m_symbolModel.hasUnigramsForKey(key) && m_symbolEnabled) { if (m_symbolModel.hasUnigramsForKey(key) && m_symbolEnabled)
{
std::vector<Gramambular::Unigram> rawSymbolUnigrams = m_symbolModel.unigramsForKey(key); std::vector<Gramambular::Unigram> rawSymbolUnigrams = m_symbolModel.unigramsForKey(key);
symbolUnigrams = filterAndTransformUnigrams(rawSymbolUnigrams, excludedValues, insertedValues); symbolUnigrams = filterAndTransformUnigrams(rawSymbolUnigrams, excludedValues, insertedValues);
} }
if (m_userSymbolModel.hasUnigramsForKey(key) && m_symbolEnabled) { if (m_userSymbolModel.hasUnigramsForKey(key) && m_symbolEnabled)
{
std::vector<Gramambular::Unigram> rawUserSymbolUnigrams = m_userSymbolModel.unigramsForKey(key); std::vector<Gramambular::Unigram> rawUserSymbolUnigrams = m_userSymbolModel.unigramsForKey(key);
userSymbolUnigrams = filterAndTransformUnigrams(rawUserSymbolUnigrams, excludedValues, insertedValues); userSymbolUnigrams = filterAndTransformUnigrams(rawUserSymbolUnigrams, excludedValues, insertedValues);
} }
if (m_cnsModel.hasUnigramsForKey(key) && m_cnsEnabled) { if (m_cnsModel.hasUnigramsForKey(key) && m_cnsEnabled)
{
std::vector<Gramambular::Unigram> rawCNSUnigrams = m_cnsModel.unigramsForKey(key); std::vector<Gramambular::Unigram> rawCNSUnigrams = m_cnsModel.unigramsForKey(key);
cnsUnigrams = filterAndTransformUnigrams(rawCNSUnigrams, excludedValues, insertedValues); cnsUnigrams = filterAndTransformUnigrams(rawCNSUnigrams, excludedValues, insertedValues);
} }
@ -203,13 +227,15 @@ const std::vector<Gramambular::Unigram> LMInstantiator::unigramsForKey(const std
return allUnigrams; return allUnigrams;
} }
bool LMInstantiator::hasUnigramsForKey(const std::string& key) bool LMInstantiator::hasUnigramsForKey(const std::string &key)
{ {
if (key == " ") { if (key == " ")
{
return true; return true;
} }
if (!m_excludedPhrases.hasUnigramsForKey(key)) { if (!m_excludedPhrases.hasUnigramsForKey(key))
{
return m_userPhrases.hasUnigramsForKey(key) || m_languageModel.hasUnigramsForKey(key); return m_userPhrases.hasUnigramsForKey(key) || m_languageModel.hasUnigramsForKey(key);
} }
@ -246,26 +272,33 @@ bool LMInstantiator::symbolEnabled()
return m_symbolEnabled; return m_symbolEnabled;
} }
const std::vector<Gramambular::Unigram> LMInstantiator::filterAndTransformUnigrams(const std::vector<Gramambular::Unigram> unigrams, const std::unordered_set<std::string>& excludedValues, std::unordered_set<std::string>& insertedValues) const std::vector<Gramambular::Unigram> LMInstantiator::filterAndTransformUnigrams(
const std::vector<Gramambular::Unigram> unigrams, const std::unordered_set<std::string> &excludedValues,
std::unordered_set<std::string> &insertedValues)
{ {
std::vector<Gramambular::Unigram> results; std::vector<Gramambular::Unigram> results;
for (auto&& unigram : unigrams) { for (auto &&unigram : unigrams)
{
// excludedValues filters out the unigrams with the original value. // excludedValues filters out the unigrams with the original value.
// insertedValues filters out the ones with the converted value // insertedValues filters out the ones with the converted value
std::string originalValue = unigram.keyValue.value; std::string originalValue = unigram.keyValue.value;
if (excludedValues.find(originalValue) != excludedValues.end()) { if (excludedValues.find(originalValue) != excludedValues.end())
{
continue; continue;
} }
std::string value = originalValue; std::string value = originalValue;
if (m_phraseReplacementEnabled) { if (m_phraseReplacementEnabled)
{
std::string replacement = m_phraseReplacement.valueForKey(value); std::string replacement = m_phraseReplacement.valueForKey(value);
if (replacement != "") { if (replacement != "")
{
value = replacement; value = replacement;
} }
} }
if (insertedValues.find(value) == insertedValues.end()) { if (insertedValues.find(value) == insertedValues.end())
{
Gramambular::Unigram g; Gramambular::Unigram g;
g.keyValue.value = value; g.keyValue.value = value;
g.keyValue.key = unigram.keyValue.key; g.keyValue.key = unigram.keyValue.key;
@ -277,12 +310,12 @@ const std::vector<Gramambular::Unigram> LMInstantiator::filterAndTransformUnigra
return results; return results;
} }
const std::vector<std::string> LMInstantiator::associatedPhrasesForKey(const std::string& key) const std::vector<std::string> LMInstantiator::associatedPhrasesForKey(const std::string &key)
{ {
return m_associatedPhrases.valuesForKey(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); return m_associatedPhrases.hasValuesForKey(key);
} }

View File

@ -1,47 +1,58 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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 #ifndef ASSOCIATEDPHRASES_H
#define ASSOCIATEDPHRASES_H #define ASSOCIATEDPHRASES_H
#include <string>
#include <map>
#include <iostream> #include <iostream>
#include <map>
#include <string>
#include <vector> #include <vector>
namespace vChewing { namespace vChewing
{
class AssociatedPhrases class AssociatedPhrases
{ {
public: public:
AssociatedPhrases(); AssociatedPhrases();
~AssociatedPhrases(); ~AssociatedPhrases();
const bool isLoaded(); const bool isLoaded();
bool open(const char *path); bool open(const char *path);
void close(); void close();
const std::vector<std::string> valuesForKey(const std::string& key); const std::vector<std::string> valuesForKey(const std::string &key);
const bool hasValuesForKey(const std::string& key); const bool hasValuesForKey(const std::string &key);
protected: protected:
struct Row { struct Row
Row(std::string_view& k, std::string_view& v) : key(k), value(v) {} {
Row(std::string_view &k, std::string_view &v) : key(k), value(v)
{
}
std::string_view key; std::string_view key;
std::string_view value; std::string_view value;
}; };
@ -53,6 +64,6 @@ protected:
size_t length; size_t length;
}; };
} } // namespace vChewing
#endif /* AssociatedPhrases_hpp */ #endif /* AssociatedPhrases_hpp */

View File

@ -1,52 +1,59 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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 "AssociatedPhrases.h"
#include "vChewing-Swift.h" #include "vChewing-Swift.h"
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h> #include <fcntl.h>
#include <fstream> #include <fstream>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h> #include <unistd.h>
#include "KeyValueBlobReader.h" #include "KeyValueBlobReader.h"
#include "LMConsolidator.h" #include "LMConsolidator.h"
namespace vChewing { namespace vChewing
{
AssociatedPhrases::AssociatedPhrases() AssociatedPhrases::AssociatedPhrases() : fd(-1), data(0), length(0)
: fd(-1)
, data(0)
, length(0)
{ {
} }
AssociatedPhrases::~AssociatedPhrases() AssociatedPhrases::~AssociatedPhrases()
{ {
if (data) { if (data)
{
close(); close();
} }
} }
const bool AssociatedPhrases::isLoaded() const bool AssociatedPhrases::isLoaded()
{ {
if (data) { if (data)
{
return true; return true;
} }
return false; return false;
@ -54,7 +61,8 @@ const bool AssociatedPhrases::isLoaded()
bool AssociatedPhrases::open(const char *path) bool AssociatedPhrases::open(const char *path)
{ {
if (data) { if (data)
{
return false; return false;
} }
@ -62,13 +70,15 @@ bool AssociatedPhrases::open(const char *path)
LMConsolidator::ConsolidateContent(path, true); LMConsolidator::ConsolidateContent(path, true);
fd = ::open(path, O_RDONLY); fd = ::open(path, O_RDONLY);
if (fd == -1) { if (fd == -1)
{
printf("open:: file not exist"); printf("open:: file not exist");
return false; return false;
} }
struct stat sb; struct stat sb;
if (fstat(fd, &sb) == -1) { if (fstat(fd, &sb) == -1)
{
printf("open:: cannot open file"); printf("open:: cannot open file");
return false; return false;
} }
@ -76,21 +86,25 @@ bool AssociatedPhrases::open(const char *path)
length = (size_t)sb.st_size; length = (size_t)sb.st_size;
data = mmap(NULL, length, PROT_READ, MAP_SHARED, fd, 0); data = mmap(NULL, length, PROT_READ, MAP_SHARED, fd, 0);
if (!data) { if (!data)
{
::close(fd); ::close(fd);
return false; return false;
} }
KeyValueBlobReader reader(static_cast<char*>(data), length); KeyValueBlobReader reader(static_cast<char *>(data), length);
KeyValueBlobReader::KeyValue keyValue; KeyValueBlobReader::KeyValue keyValue;
KeyValueBlobReader::State state; 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); keyRowMap[keyValue.key].emplace_back(keyValue.key, keyValue.value);
} }
// 下面這一段或許可以做成開關、來詢問是否對使用者語彙採取寬鬆策略(哪怕有行內容寫錯也會放行) // 下面這一段或許可以做成開關、來詢問是否對使用者語彙採取寬鬆策略(哪怕有行內容寫錯也會放行)
if (state == KeyValueBlobReader::State::ERROR) { if (state == KeyValueBlobReader::State::ERROR)
{
// close(); // 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 false;
} }
return true; return true;
@ -98,7 +112,8 @@ bool AssociatedPhrases::open(const char *path)
void AssociatedPhrases::close() void AssociatedPhrases::close()
{ {
if (data) { if (data)
{
munmap(data, length); munmap(data, length);
::close(fd); ::close(fd);
data = 0; data = 0;
@ -107,13 +122,15 @@ void AssociatedPhrases::close()
keyRowMap.clear(); keyRowMap.clear();
} }
const std::vector<std::string> AssociatedPhrases::valuesForKey(const std::string& key) const std::vector<std::string> AssociatedPhrases::valuesForKey(const std::string &key)
{ {
std::vector<std::string> v; std::vector<std::string> v;
auto iter = keyRowMap.find(key); auto iter = keyRowMap.find(key);
if (iter != keyRowMap.end()) { if (iter != keyRowMap.end())
const std::vector<Row>& rows = iter->second; {
for (const auto& row : rows) { const std::vector<Row> &rows = iter->second;
for (const auto &row : rows)
{
std::string_view value = row.value; std::string_view value = row.value;
v.push_back({value.data(), value.size()}); v.push_back({value.data(), value.size()});
} }
@ -121,9 +138,9 @@ const std::vector<std::string> AssociatedPhrases::valuesForKey(const std::string
return v; return v;
} }
const bool AssociatedPhrases::hasValuesForKey(const std::string& key) const bool AssociatedPhrases::hasValuesForKey(const std::string &key)
{ {
return keyRowMap.find(key) != keyRowMap.end(); return keyRowMap.find(key) != keyRowMap.end();
} }
}; // namespace vChewing }; // namespace vChewing

View File

@ -1,30 +1,37 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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 #ifndef CoreLM_H
#define CoreLM_H #define CoreLM_H
#include "LanguageModel.h" #include "LanguageModel.h"
#include <iostream>
#include <map>
#include <string> #include <string>
#include <vector> #include <vector>
#include <map>
#include <iostream>
// this class relies on the fact that we have a space-separated data // 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 // 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 std;
using namespace Gramambular; using namespace Gramambular;
namespace vChewing { namespace vChewing
{
class CoreLM : public Gramambular::LanguageModel { class CoreLM : public Gramambular::LanguageModel
public: {
public:
CoreLM(); CoreLM();
~CoreLM(); ~CoreLM();
@ -45,20 +54,21 @@ public:
void close(); void close();
void dump(); void dump();
virtual const std::vector<Gramambular::Bigram> bigramsForKeys(const string& preceedingKey, const string& key); virtual const std::vector<Gramambular::Bigram> bigramsForKeys(const string &preceedingKey, const string &key);
virtual const std::vector<Gramambular::Unigram> unigramsForKey(const string& key); virtual const std::vector<Gramambular::Unigram> unigramsForKey(const string &key);
virtual bool hasUnigramsForKey(const string& key); virtual bool hasUnigramsForKey(const string &key);
protected: protected:
struct CStringCmp 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; return strcmp(s1, s2) < 0;
} }
}; };
struct Row { struct Row
{
const char *key; const char *key;
const char *value; const char *value;
const char *logProbability; const char *logProbability;

View File

@ -1,50 +1,56 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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 "CoreLM.h"
#include <sys/mman.h> #include "vChewing-Swift.h"
#include <sys/stat.h>
#include <fcntl.h> #include <fcntl.h>
#include <fstream> #include <fstream>
#include <unistd.h> #include <sys/mman.h>
#include <sys/stat.h>
#include <syslog.h> #include <syslog.h>
#include "vChewing-Swift.h" #include <unistd.h>
using namespace Gramambular; using namespace Gramambular;
vChewing::CoreLM::CoreLM() vChewing::CoreLM::CoreLM() : fd(-1), data(0), length(0)
: fd(-1)
, data(0)
, length(0)
{ {
} }
vChewing::CoreLM::~CoreLM() vChewing::CoreLM::~CoreLM()
{ {
if (data) { if (data)
{
close(); close();
} }
} }
bool vChewing::CoreLM::isLoaded() bool vChewing::CoreLM::isLoaded()
{ {
if (data) { if (data)
{
return true; return true;
} }
return false; return false;
@ -52,24 +58,28 @@ bool vChewing::CoreLM::isLoaded()
bool vChewing::CoreLM::open(const char *path) bool vChewing::CoreLM::open(const char *path)
{ {
if (data) { if (data)
{
return false; return false;
} }
fd = ::open(path, O_RDONLY); fd = ::open(path, O_RDONLY);
if (fd == -1) { if (fd == -1)
{
return false; return false;
} }
struct stat sb; struct stat sb;
if (fstat(fd, &sb) == -1) { if (fstat(fd, &sb) == -1)
{
return false; return false;
} }
length = (size_t)sb.st_size; length = (size_t)sb.st_size;
data = mmap(NULL, length, PROT_WRITE, MAP_PRIVATE, fd, 0); data = mmap(NULL, length, PROT_WRITE, MAP_PRIVATE, fd, 0);
if (!data) { if (!data)
{
::close(fd); ::close(fd);
return false; return false;
} }
@ -117,18 +127,22 @@ bool vChewing::CoreLM::open(const char *path)
start: start:
// EOF -> end // EOF -> end
if (head == end) { if (head == end)
{
goto end; goto end;
} }
c = *head; c = *head;
// \s -> error // \s -> error
if (c == ' ') { if (c == ' ')
if (mgrPrefs.isDebugModeEnabled) syslog(LOG_CONS, "vChewingDebug: CoreLM // Start: \\s -> error"); {
if (mgrPrefs.isDebugModeEnabled)
syslog(LOG_CONS, "vChewingDebug: CoreLM // Start: \\s -> error");
goto error; goto error;
} }
// \n -> start // \n -> start
else if (c == '\n') { else if (c == '\n')
{
head++; head++;
goto start; goto start;
} }
@ -140,19 +154,24 @@ start:
state1: state1:
// EOF -> error // EOF -> error
if (head == end) { if (head == end)
if (mgrPrefs.isDebugModeEnabled) syslog(LOG_CONS, "vChewingDebug: CoreLM // state 1: EOF -> error"); {
if (mgrPrefs.isDebugModeEnabled)
syslog(LOG_CONS, "vChewingDebug: CoreLM // state 1: EOF -> error");
goto error; goto error;
} }
c = *head; c = *head;
// \n -> error // \n -> error
if (c == '\n') { if (c == '\n')
if (mgrPrefs.isDebugModeEnabled) syslog(LOG_CONS, "vChewingDebug: CoreLM // state 1: \\n -> error"); {
if (mgrPrefs.isDebugModeEnabled)
syslog(LOG_CONS, "vChewingDebug: CoreLM // state 1: \\n -> error");
goto error; goto error;
} }
// \s -> state2 + zero out ending + record column start // \s -> state2 + zero out ending + record column start
else if (c == ' ') { else if (c == ' ')
{
*head = 0; *head = 0;
head++; head++;
row.key = head; row.key = head;
@ -165,15 +184,19 @@ state1:
state2: state2:
// eof -> error // eof -> error
if (head == end) { if (head == end)
if (mgrPrefs.isDebugModeEnabled) syslog(LOG_CONS, "vChewingDebug: CoreLM // state 2: EOF -> error"); {
if (mgrPrefs.isDebugModeEnabled)
syslog(LOG_CONS, "vChewingDebug: CoreLM // state 2: EOF -> error");
goto error; goto error;
} }
c = *head; c = *head;
// \n, \s -> error // \n, \s -> error
if (c == '\n' || c == ' ') { if (c == '\n' || c == ' ')
if (mgrPrefs.isDebugModeEnabled) syslog(LOG_CONS, "vChewingDebug: CoreLM // state 2: \\n \\s -> error"); {
if (mgrPrefs.isDebugModeEnabled)
syslog(LOG_CONS, "vChewingDebug: CoreLM // state 2: \\n \\s -> error");
goto error; goto error;
} }
@ -184,20 +207,25 @@ state2:
state3: state3:
// eof -> error // eof -> error
if (head == end) { if (head == end)
if (mgrPrefs.isDebugModeEnabled) syslog(LOG_CONS, "vChewingDebug: CoreLM // state 3: EOF -> error"); {
if (mgrPrefs.isDebugModeEnabled)
syslog(LOG_CONS, "vChewingDebug: CoreLM // state 3: EOF -> error");
goto error; goto error;
} }
c = *head; c = *head;
// \n -> error // \n -> error
if (c == '\n') { if (c == '\n')
if (mgrPrefs.isDebugModeEnabled) syslog(LOG_CONS, "vChewingDebug: CoreLM // state 3: \\n -> error"); {
if (mgrPrefs.isDebugModeEnabled)
syslog(LOG_CONS, "vChewingDebug: CoreLM // state 3: \\n -> error");
goto error; goto error;
} }
// \s -> state4 + zero out ending + record column start // \s -> state4 + zero out ending + record column start
else if (c == ' ') { else if (c == ' ')
{
*head = 0; *head = 0;
head++; head++;
row.logProbability = head; row.logProbability = head;
@ -210,15 +238,19 @@ state3:
state4: state4:
// eof -> error // eof -> error
if (head == end) { if (head == end)
if (mgrPrefs.isDebugModeEnabled) syslog(LOG_CONS, "vChewingDebug: CoreLM // state 4: EOF -> error"); {
if (mgrPrefs.isDebugModeEnabled)
syslog(LOG_CONS, "vChewingDebug: CoreLM // state 4: EOF -> error");
goto error; goto error;
} }
c = *head; c = *head;
// \n, \s -> error // \n, \s -> error
if (c == '\n' || c == ' ') { if (c == '\n' || c == ' ')
if (mgrPrefs.isDebugModeEnabled) syslog(LOG_CONS, "vChewingDebug: CoreLM // state 4: \\n \\s -> error"); {
if (mgrPrefs.isDebugModeEnabled)
syslog(LOG_CONS, "vChewingDebug: CoreLM // state 4: \\n \\s -> error");
goto error; goto error;
} }
@ -227,22 +259,26 @@ state4:
// fall through to state 5 // fall through to state 5
state5: state5:
// eof -> error // eof -> error
if (head == end) { if (head == end)
if (mgrPrefs.isDebugModeEnabled) syslog(LOG_CONS, "vChewingDebug: CoreLM // state 5: EOF -> error"); {
if (mgrPrefs.isDebugModeEnabled)
syslog(LOG_CONS, "vChewingDebug: CoreLM // state 5: EOF -> error");
goto error; goto error;
} }
c = *head; c = *head;
// \s -> error // \s -> error
if (c == ' ') { if (c == ' ')
if (mgrPrefs.isDebugModeEnabled) syslog(LOG_CONS, "vChewingDebug: CoreLM // state 5: \\s -> error"); {
if (mgrPrefs.isDebugModeEnabled)
syslog(LOG_CONS, "vChewingDebug: CoreLM // state 5: \\s -> error");
goto error; goto error;
} }
// \n -> start // \n -> start
else if (c == '\n') { else if (c == '\n')
{
*head = 0; *head = 0;
head++; head++;
keyRowMap[row.key].push_back(row); keyRowMap[row.key].push_back(row);
@ -265,13 +301,15 @@ end:
emptyRow.value = space; emptyRow.value = space;
emptyRow.logProbability = zero; emptyRow.logProbability = zero;
keyRowMap[space].push_back(emptyRow); 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; return true;
} }
void vChewing::CoreLM::close() void vChewing::CoreLM::close()
{ {
if (data) { if (data)
{
munmap(data, length); munmap(data, length);
::close(fd); ::close(fd);
data = 0; data = 0;
@ -283,30 +321,34 @@ void vChewing::CoreLM::close()
void vChewing::CoreLM::dump() void vChewing::CoreLM::dump()
{ {
size_t rows = 0; size_t rows = 0;
for (map<const char *, vector<Row> >::const_iterator i = keyRowMap.begin(), e = keyRowMap.end(); i != e; ++i) { for (map<const char *, vector<Row>>::const_iterator i = keyRowMap.begin(), e = keyRowMap.end(); i != e; ++i)
const vector<Row>& r = (*i).second; {
for (vector<Row>::const_iterator ri = r.begin(), re = r.end(); ri != re; ++ri) { const vector<Row> &r = (*i).second;
const Row& row = *ri; for (vector<Row>::const_iterator ri = r.begin(), re = r.end(); ri != re; ++ri)
{
const Row &row = *ri;
cerr << row.key << " " << row.value << " " << row.logProbability << "\n"; cerr << row.key << " " << row.value << " " << row.logProbability << "\n";
rows++; rows++;
} }
} }
} }
const std::vector<Gramambular::Bigram> vChewing::CoreLM::bigramsForKeys(const string& preceedingKey, const string& key) const std::vector<Gramambular::Bigram> vChewing::CoreLM::bigramsForKeys(const string &preceedingKey, const string &key)
{ {
return std::vector<Gramambular::Bigram>(); return std::vector<Gramambular::Bigram>();
} }
const std::vector<Gramambular::Unigram> vChewing::CoreLM::unigramsForKey(const string& key) const std::vector<Gramambular::Unigram> vChewing::CoreLM::unigramsForKey(const string &key)
{ {
std::vector<Gramambular::Unigram> v; std::vector<Gramambular::Unigram> v;
map<const char *, vector<Row> >::const_iterator i = keyRowMap.find(key.c_str()); map<const char *, vector<Row>>::const_iterator i = keyRowMap.find(key.c_str());
if (i != keyRowMap.end()) { if (i != keyRowMap.end())
for (vector<Row>::const_iterator ri = (*i).second.begin(), re = (*i).second.end(); ri != re; ++ri) { {
for (vector<Row>::const_iterator ri = (*i).second.begin(), re = (*i).second.end(); ri != re; ++ri)
{
Unigram g; Unigram g;
const Row& r = *ri; const Row &r = *ri;
g.keyValue.key = r.key; g.keyValue.key = r.key;
g.keyValue.value = r.value; g.keyValue.value = r.value;
g.score = atof(r.logProbability); g.score = atof(r.logProbability);
@ -317,7 +359,7 @@ const std::vector<Gramambular::Unigram> vChewing::CoreLM::unigramsForKey(const s
return v; return v;
} }
bool vChewing::CoreLM::hasUnigramsForKey(const string& key) bool vChewing::CoreLM::hasUnigramsForKey(const string &key)
{ {
return keyRowMap.find(key.c_str()) != keyRowMap.end(); return keyRowMap.find(key.c_str()) != keyRowMap.end();
} }

View File

@ -1,44 +1,54 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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 #ifndef CNSLM_H
#define CNSLM_H #define CNSLM_H
#include <string>
#include <map>
#include <iostream>
#include "LanguageModel.h" #include "LanguageModel.h"
#include "UserPhrasesLM.h" #include "UserPhrasesLM.h"
#include <iostream>
#include <map>
#include <string>
namespace vChewing { namespace vChewing
class CNSLM: public UserPhrasesLM
{ {
public:
virtual bool allowConsolidation() override { class CNSLM : public UserPhrasesLM
{
public:
virtual bool allowConsolidation() override
{
return false; return false;
} }
virtual float overridedValue() override { virtual float overridedValue() override
{
return -11.0; return -11.0;
} }
}; };
} } // namespace vChewing
#endif #endif

View File

@ -1,44 +1,54 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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 #ifndef SYMBOLLM_H
#define SYMBOLLM_H #define SYMBOLLM_H
#include <string>
#include <map>
#include <iostream>
#include "LanguageModel.h" #include "LanguageModel.h"
#include "UserPhrasesLM.h" #include "UserPhrasesLM.h"
#include <iostream>
#include <map>
#include <string>
namespace vChewing { namespace vChewing
class SymbolLM: public UserPhrasesLM
{ {
public:
virtual bool allowConsolidation() override { class SymbolLM : public UserPhrasesLM
{
public:
virtual bool allowConsolidation() override
{
return false; return false;
} }
virtual float overridedValue() override { virtual float overridedValue() override
{
return -13.0; return -13.0;
} }
}; };
} } // namespace vChewing
#endif #endif

View File

@ -1,44 +1,54 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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 #ifndef USERSYMBOLLM_H
#define USERSYMBOLLM_H #define USERSYMBOLLM_H
#include <string>
#include <map>
#include <iostream>
#include "LanguageModel.h" #include "LanguageModel.h"
#include "UserPhrasesLM.h" #include "UserPhrasesLM.h"
#include <iostream>
#include <map>
#include <string>
namespace vChewing { namespace vChewing
class UserSymbolLM: public UserPhrasesLM
{ {
public:
virtual bool allowConsolidation() override { class UserSymbolLM : public UserPhrasesLM
{
public:
virtual bool allowConsolidation() override
{
return true; return true;
} }
virtual float overridedValue() override { virtual float overridedValue() override
{
return -12.0; return -12.0;
} }
}; };
} } // namespace vChewing
#endif #endif

View File

@ -1,20 +1,27 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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" #include "ParselessLM.h"
@ -26,29 +33,36 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR TH
#include <memory> #include <memory>
vChewing::ParselessLM::~ParselessLM() { close(); } vChewing::ParselessLM::~ParselessLM()
{
close();
}
bool vChewing::ParselessLM::isLoaded() bool vChewing::ParselessLM::isLoaded()
{ {
if (data_) { if (data_)
{
return true; return true;
} }
return false; 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; return false;
} }
fd_ = ::open(path.data(), O_RDONLY); fd_ = ::open(path.data(), O_RDONLY);
if (fd_ == -1) { if (fd_ == -1)
{
return false; return false;
} }
struct stat sb; struct stat sb;
if (fstat(fd_, &sb) == -1) { if (fstat(fd_, &sb) == -1)
{
::close(fd_); ::close(fd_);
fd_ = -1; fd_ = -1;
return false; return false;
@ -57,21 +71,22 @@ bool vChewing::ParselessLM::open(const std::string_view& path)
length_ = static_cast<size_t>(sb.st_size); length_ = static_cast<size_t>(sb.st_size);
data_ = mmap(NULL, length_, PROT_READ, MAP_SHARED, fd_, 0); data_ = mmap(NULL, length_, PROT_READ, MAP_SHARED, fd_, 0);
if (data_ == nullptr) { if (data_ == nullptr)
{
::close(fd_); ::close(fd_);
fd_ = -1; fd_ = -1;
length_ = 0; length_ = 0;
return false; return false;
} }
db_ = std::unique_ptr<ParselessPhraseDB>(new ParselessPhraseDB( db_ = std::unique_ptr<ParselessPhraseDB>(new ParselessPhraseDB(static_cast<char *>(data_), length_));
static_cast<char*>(data_), length_));
return true; return true;
} }
void vChewing::ParselessLM::close() void vChewing::ParselessLM::close()
{ {
if (data_ != nullptr) { if (data_ != nullptr)
{
munmap(data_, length_); munmap(data_, length_);
::close(fd_); ::close(fd_);
fd_ = -1; fd_ = -1;
@ -80,55 +95,61 @@ void vChewing::ParselessLM::close()
} }
} }
const std::vector<Gramambular::Bigram> const std::vector<Gramambular::Bigram> vChewing::ParselessLM::bigramsForKeys(const std::string &preceedingKey,
vChewing::ParselessLM::bigramsForKeys( const std::string &key)
const std::string& preceedingKey, const std::string& key)
{ {
return std::vector<Gramambular::Bigram>(); return std::vector<Gramambular::Bigram>();
} }
const std::vector<Gramambular::Unigram> const std::vector<Gramambular::Unigram> vChewing::ParselessLM::unigramsForKey(const std::string &key)
vChewing::ParselessLM::unigramsForKey(const std::string& key)
{ {
if (db_ == nullptr) { if (db_ == nullptr)
{
return std::vector<Gramambular::Unigram>(); return std::vector<Gramambular::Unigram>();
} }
std::vector<Gramambular::Unigram> results; std::vector<Gramambular::Unigram> results;
for (const auto& row : db_->findRows(key + " ")) { for (const auto &row : db_->findRows(key + " "))
{
Gramambular::Unigram unigram; Gramambular::Unigram unigram;
// Move ahead until we encounter the first space. This is the key. // Move ahead until we encounter the first space. This is the key.
auto it = row.begin(); auto it = row.begin();
while (it != row.end() && *it != ' ') { while (it != row.end() && *it != ' ')
{
++it; ++it;
} }
unigram.keyValue.key = std::string(row.begin(), it); unigram.keyValue.key = std::string(row.begin(), it);
// Read past the space. // Read past the space.
if (it != row.end()) { if (it != row.end())
{
++it; ++it;
} }
if (it != row.end()) { if (it != row.end())
{
// Now it is the start of the value portion. // Now it is the start of the value portion.
auto value_begin = it; auto value_begin = it;
// Move ahead until we encounter the second space. This is the // Move ahead until we encounter the second space. This is the
// value. // value.
while (it != row.end() && *it != ' ') { while (it != row.end() && *it != ' ')
{
++it; ++it;
} }
unigram.keyValue.value = std::string(value_begin, it); unigram.keyValue.value = std::string(value_begin, it);
} }
// Read past the space. The remainder, if it exists, is the score. // Read past the space. The remainder, if it exists, is the score.
if (it != row.end()) { if (it != row.end())
{
++it; ++it;
} }
if (it != row.end()) { if (it != row.end())
{
unigram.score = std::stod(std::string(it, row.end())); unigram.score = std::stod(std::string(it, row.end()));
} }
results.push_back(unigram); results.push_back(unigram);
@ -136,9 +157,10 @@ vChewing::ParselessLM::unigramsForKey(const std::string& key)
return results; 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; return false;
} }

View File

@ -1,20 +1,27 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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_ #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 "LanguageModel.h"
#include "ParselessPhraseDB.h" #include "ParselessPhraseDB.h"
namespace vChewing { namespace vChewing
{
class ParselessLM : public Gramambular::LanguageModel { class ParselessLM : public Gramambular::LanguageModel
public: {
public:
~ParselessLM() override; ~ParselessLM() override;
bool isLoaded(); bool isLoaded();
bool open(const std::string_view& path); bool open(const std::string_view &path);
void close(); void close();
const std::vector<Gramambular::Bigram> bigramsForKeys( const std::vector<Gramambular::Bigram> bigramsForKeys(const std::string &preceedingKey,
const std::string& preceedingKey, const std::string& key) override; const std::string &key) override;
const std::vector<Gramambular::Unigram> unigramsForKey( const std::vector<Gramambular::Unigram> unigramsForKey(const std::string &key) override;
const std::string& key) override; bool hasUnigramsForKey(const std::string &key) override;
bool hasUnigramsForKey(const std::string& key) override;
private: private:
int fd_ = -1; int fd_ = -1;
void* data_ = nullptr; void *data_ = nullptr;
size_t length_ = 0; size_t length_ = 0;
std::unique_ptr<ParselessPhraseDB> db_; std::unique_ptr<ParselessPhraseDB> db_;
}; };

View File

@ -1,20 +1,27 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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" #include "ParselessPhraseDB.h"
@ -22,35 +29,35 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR TH
#include <cassert> #include <cassert>
#include <cstring> #include <cstring>
namespace vChewing { namespace vChewing
{
ParselessPhraseDB::ParselessPhraseDB( ParselessPhraseDB::ParselessPhraseDB(const char *buf, size_t length) : begin_(buf), end_(buf + length)
const char* buf, size_t length)
: begin_(buf)
, end_(buf + length)
{ {
} }
std::vector<std::string_view> ParselessPhraseDB::findRows( std::vector<std::string_view> ParselessPhraseDB::findRows(const std::string_view &key)
const std::string_view& key)
{ {
std::vector<std::string_view> rows; std::vector<std::string_view> rows;
const char* ptr = findFirstMatchingLine(key); const char *ptr = findFirstMatchingLine(key);
if (ptr == nullptr) { if (ptr == nullptr)
{
return rows; return rows;
} }
while (ptr + key.length() <= end_ while (ptr + key.length() <= end_ && memcmp(ptr, key.data(), key.length()) == 0)
&& memcmp(ptr, key.data(), key.length()) == 0) { {
const char* eol = ptr; const char *eol = ptr;
while (eol != end_ && *eol != '\n') { while (eol != end_ && *eol != '\n')
{
++eol; ++eol;
} }
rows.emplace_back(ptr, eol - ptr); rows.emplace_back(ptr, eol - ptr);
if (eol == end_) { if (eol == end_)
{
break; break;
} }
@ -66,71 +73,83 @@ std::vector<std::string_view> ParselessPhraseDB::findRows(
// current line is actually the first matching line: if the previous line is // 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 // less to the key and the current line starts exactly with the key, then
// the current line is the first matching line. // the current line is the first matching line.
const char* ParselessPhraseDB::findFirstMatchingLine( const char *ParselessPhraseDB::findFirstMatchingLine(const std::string_view &key)
const std::string_view& key)
{ {
if (key.empty()) { if (key.empty())
{
return begin_; return begin_;
} }
const char* top = begin_; const char *top = begin_;
const char* bottom = end_; const char *bottom = end_;
while (top < bottom) { while (top < bottom)
const char* mid = top + (bottom - top) / 2; {
const char* ptr = mid; const char *mid = top + (bottom - top) / 2;
const char *ptr = mid;
if (ptr != begin_) { if (ptr != begin_)
{
--ptr; --ptr;
} }
while (ptr != begin_ && *ptr != '\n') { while (ptr != begin_ && *ptr != '\n')
{
--ptr; --ptr;
} }
const char* prev = nullptr; const char *prev = nullptr;
if (*ptr == '\n') { if (*ptr == '\n')
{
prev = ptr; prev = ptr;
++ptr; ++ptr;
} }
// ptr is now in the "current" line we're interested in. // 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. // not enough data to compare at this point, bail.
break; break;
} }
int current_cmp = memcmp(ptr, key.data(), key.length()); int current_cmp = memcmp(ptr, key.data(), key.length());
if (current_cmp > 0) { if (current_cmp > 0)
{
bottom = mid - 1; bottom = mid - 1;
continue; continue;
} }
if (current_cmp < 0) { if (current_cmp < 0)
{
top = mid + 1; top = mid + 1;
continue; continue;
} }
if (!prev) { if (!prev)
{
return ptr; return ptr;
} }
// Move the prev so that it reaches the previous line. // Move the prev so that it reaches the previous line.
if (prev != begin_) { if (prev != begin_)
{
--prev; --prev;
} }
while (prev != begin_ && *prev != '\n') { while (prev != begin_ && *prev != '\n')
{
--prev; --prev;
} }
if (*prev == '\n') { if (*prev == '\n')
{
++prev; ++prev;
} }
int prev_cmp = memcmp(prev, key.data(), key.length()); int prev_cmp = memcmp(prev, key.data(), key.length());
// This is the first occurrence. // This is the first occurrence.
if (prev_cmp < 0 && current_cmp == 0) { if (prev_cmp < 0 && current_cmp == 0)
{
return ptr; return ptr;
} }

View File

@ -1,20 +1,27 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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_ #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 <string> #include <string>
#include <vector> #include <vector>
namespace vChewing { namespace vChewing
{
// Defines phrase database that consists of (key, value, score) rows that are // 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 // 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 // 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 // that the database is already sorted, and binary search is used to find the
// rows. // rows.
class ParselessPhraseDB { class ParselessPhraseDB
public: {
ParselessPhraseDB( public:
const char* buf, size_t length); ParselessPhraseDB(const char *buf, size_t length);
// Find the rows that match the key. Note that prefix match is used. If you // 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) // need exact match, the key will need to have a delimiter (usually a space)
// at the end. // at the end.
std::vector<std::string_view> findRows(const std::string_view& key); std::vector<std::string_view> findRows(const std::string_view &key);
const char* findFirstMatchingLine(const std::string_view& key); const char *findFirstMatchingLine(const std::string_view &key);
private: private:
const char* begin_; const char *begin_;
const char* end_; const char *end_;
}; };
}; // namespace vChewing }; // namespace vChewing

View File

@ -1,48 +1,56 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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 #ifndef PHRASEREPLACEMENTMAP_H
#define PHRASEREPLACEMENTMAP_H #define PHRASEREPLACEMENTMAP_H
#include <string>
#include <map>
#include <iostream> #include <iostream>
#include <map>
#include <string>
namespace vChewing { namespace vChewing
{
class PhraseReplacementMap class PhraseReplacementMap
{ {
public: public:
PhraseReplacementMap(); PhraseReplacementMap();
~PhraseReplacementMap(); ~PhraseReplacementMap();
bool open(const char *path); bool open(const char *path);
void close(); void close();
const std::string valueForKey(const std::string& key); const std::string valueForKey(const std::string &key);
protected: protected:
std::map<std::string_view, std::string_view> keyValueMap; std::map<std::string_view, std::string_view> keyValueMap;
int fd; int fd;
void *data; void *data;
size_t length; size_t length;
}; };
} } // namespace vChewing
#endif #endif

View File

@ -1,55 +1,62 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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 "PhraseReplacementMap.h"
#include "vChewing-Swift.h" #include "vChewing-Swift.h"
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h> #include <fcntl.h>
#include <fstream> #include <fstream>
#include <unistd.h> #include <sys/mman.h>
#include <sys/stat.h>
#include <syslog.h> #include <syslog.h>
#include <unistd.h>
#include "KeyValueBlobReader.h" #include "KeyValueBlobReader.h"
#include "LMConsolidator.h" #include "LMConsolidator.h"
namespace vChewing { namespace vChewing
{
using std::string; using std::string;
PhraseReplacementMap::PhraseReplacementMap() PhraseReplacementMap::PhraseReplacementMap() : fd(-1), data(0), length(0)
: fd(-1)
, data(0)
, length(0)
{ {
} }
PhraseReplacementMap::~PhraseReplacementMap() PhraseReplacementMap::~PhraseReplacementMap()
{ {
if (data) { if (data)
{
close(); close();
} }
} }
bool PhraseReplacementMap::open(const char *path) bool PhraseReplacementMap::open(const char *path)
{ {
if (data) { if (data)
{
return false; return false;
} }
@ -57,13 +64,15 @@ bool PhraseReplacementMap::open(const char *path)
LMConsolidator::ConsolidateContent(path, true); LMConsolidator::ConsolidateContent(path, true);
fd = ::open(path, O_RDONLY); fd = ::open(path, O_RDONLY);
if (fd == -1) { if (fd == -1)
{
printf("open:: file not exist"); printf("open:: file not exist");
return false; return false;
} }
struct stat sb; struct stat sb;
if (fstat(fd, &sb) == -1) { if (fstat(fd, &sb) == -1)
{
printf("open:: cannot open file"); printf("open:: cannot open file");
return false; return false;
} }
@ -71,21 +80,25 @@ bool PhraseReplacementMap::open(const char *path)
length = (size_t)sb.st_size; length = (size_t)sb.st_size;
data = mmap(NULL, length, PROT_READ, MAP_SHARED, fd, 0); data = mmap(NULL, length, PROT_READ, MAP_SHARED, fd, 0);
if (!data) { if (!data)
{
::close(fd); ::close(fd);
return false; return false;
} }
KeyValueBlobReader reader(static_cast<char*>(data), length); KeyValueBlobReader reader(static_cast<char *>(data), length);
KeyValueBlobReader::KeyValue keyValue; KeyValueBlobReader::KeyValue keyValue;
KeyValueBlobReader::State state; 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; keyValueMap[keyValue.key] = keyValue.value;
} }
// 下面這一段或許可以做成開關、來詢問是否對使用者語彙採取寬鬆策略(哪怕有行內容寫錯也會放行) // 下面這一段或許可以做成開關、來詢問是否對使用者語彙採取寬鬆策略(哪怕有行內容寫錯也會放行)
if (state == KeyValueBlobReader::State::ERROR) { if (state == KeyValueBlobReader::State::ERROR)
{
// close(); // 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 false;
} }
return true; return true;
@ -93,7 +106,8 @@ bool PhraseReplacementMap::open(const char *path)
void PhraseReplacementMap::close() void PhraseReplacementMap::close()
{ {
if (data) { if (data)
{
munmap(data, length); munmap(data, length);
::close(fd); ::close(fd);
data = 0; data = 0;
@ -102,15 +116,15 @@ void PhraseReplacementMap::close()
keyValueMap.clear(); 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); auto iter = keyValueMap.find(key);
if (iter != keyValueMap.end()) { if (iter != keyValueMap.end())
{
const std::string_view v = iter->second; const std::string_view v = iter->second;
return {v.data(), v.size()}; return {v.data(), v.size()};
} }
return string(""); return string("");
} }
} }

View File

@ -1,20 +1,27 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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" #include "UserOverrideModel.h"
@ -23,88 +30,84 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR TH
#include <cmath> #include <cmath>
#include <sstream> #include <sstream>
namespace vChewing { namespace vChewing
{
// About 20 generations. // About 20 generations.
static const double DecayThreshould = 1.0 / 1048576.0; static const double DecayThreshould = 1.0 / 1048576.0;
static double Score(size_t eventCount, static double Score(size_t eventCount, size_t totalCount, double eventTimestamp, double timestamp, double lambda);
size_t totalCount, static bool IsEndingPunctuation(const std::string &value);
double eventTimestamp, static std::string WalkedNodesToKey(const std::vector<Gramambular::NodeAnchor> &walkedNodes, size_t cursorIndex);
double timestamp,
double lambda);
static bool IsEndingPunctuation(const std::string& value);
static std::string WalkedNodesToKey(const std::vector<Gramambular::NodeAnchor>& walkedNodes,
size_t cursorIndex);
UserOverrideModel::UserOverrideModel(size_t capacity, double decayConstant) UserOverrideModel::UserOverrideModel(size_t capacity, double decayConstant) : m_capacity(capacity)
: m_capacity(capacity) { {
assert(m_capacity > 0); assert(m_capacity > 0);
m_decayExponent = log(0.5) / decayConstant; m_decayExponent = log(0.5) / decayConstant;
} }
void UserOverrideModel::observe(const std::vector<Gramambular::NodeAnchor>& walkedNodes, void UserOverrideModel::observe(const std::vector<Gramambular::NodeAnchor> &walkedNodes, size_t cursorIndex,
size_t cursorIndex, const std::string &candidate, double timestamp)
const std::string& candidate, {
double timestamp) {
std::string key = WalkedNodesToKey(walkedNodes, cursorIndex); std::string key = WalkedNodesToKey(walkedNodes, cursorIndex);
auto mapIter = m_lruMap.find(key); auto mapIter = m_lruMap.find(key);
if (mapIter == m_lruMap.end()) { if (mapIter == m_lruMap.end())
{
auto keyValuePair = KeyObservationPair(key, Observation()); auto keyValuePair = KeyObservationPair(key, Observation());
Observation& observation = keyValuePair.second; Observation &observation = keyValuePair.second;
observation.update(candidate, timestamp); observation.update(candidate, timestamp);
m_lruList.push_front(keyValuePair); m_lruList.push_front(keyValuePair);
auto listIter = m_lruList.begin(); auto listIter = m_lruList.begin();
auto lruKeyValue = std::pair<std::string, auto lruKeyValue = std::pair<std::string, std::list<KeyObservationPair>::iterator>(key, listIter);
std::list<KeyObservationPair>::iterator>(key, listIter);
m_lruMap.insert(lruKeyValue); m_lruMap.insert(lruKeyValue);
if (m_lruList.size() > m_capacity) { if (m_lruList.size() > m_capacity)
{
auto lastKeyValuePair = m_lruList.end(); auto lastKeyValuePair = m_lruList.end();
--lastKeyValuePair; --lastKeyValuePair;
m_lruMap.erase(lastKeyValuePair->first); m_lruMap.erase(lastKeyValuePair->first);
m_lruList.pop_back(); m_lruList.pop_back();
} }
} else { }
else
{
auto listIter = mapIter->second; auto listIter = mapIter->second;
m_lruList.splice(m_lruList.begin(), m_lruList, listIter); m_lruList.splice(m_lruList.begin(), m_lruList, listIter);
auto& keyValuePair = *listIter; auto &keyValuePair = *listIter;
Observation& observation = keyValuePair.second; Observation &observation = keyValuePair.second;
observation.update(candidate, timestamp); observation.update(candidate, timestamp);
} }
} }
std::string UserOverrideModel::suggest(const std::vector<Gramambular::NodeAnchor>& walkedNodes, std::string UserOverrideModel::suggest(const std::vector<Gramambular::NodeAnchor> &walkedNodes, size_t cursorIndex,
size_t cursorIndex, double timestamp)
double timestamp) { {
std::string key = WalkedNodesToKey(walkedNodes, cursorIndex); std::string key = WalkedNodesToKey(walkedNodes, cursorIndex);
auto mapIter = m_lruMap.find(key); auto mapIter = m_lruMap.find(key);
if (mapIter == m_lruMap.end()) { if (mapIter == m_lruMap.end())
{
return std::string(); return std::string();
} }
auto listIter = mapIter->second; auto listIter = mapIter->second;
auto& keyValuePair = *listIter; auto &keyValuePair = *listIter;
const Observation& observation = keyValuePair.second; const Observation &observation = keyValuePair.second;
std::string candidate; std::string candidate;
double score = 0.0; double score = 0.0;
for (auto i = observation.overrides.begin(); for (auto i = observation.overrides.begin(); i != observation.overrides.end(); ++i)
i != observation.overrides.end(); {
++i) { const Override &o = i->second;
const Override& o = i->second; double overrideScore = Score(o.count, observation.count, o.timestamp, timestamp, m_decayExponent);
double overrideScore = Score(o.count, if (overrideScore == 0.0)
observation.count, {
o.timestamp,
timestamp,
m_decayExponent);
if (overrideScore == 0.0) {
continue; continue;
} }
if (overrideScore > score) { if (overrideScore > score)
{
candidate = i->first; candidate = i->first;
score = overrideScore; score = overrideScore;
} }
@ -112,21 +115,19 @@ std::string UserOverrideModel::suggest(const std::vector<Gramambular::NodeAnchor
return candidate; return candidate;
} }
void UserOverrideModel::Observation::update(const std::string& candidate, void UserOverrideModel::Observation::update(const std::string &candidate, double timestamp)
double timestamp) { {
count++; count++;
auto& o = overrides[candidate]; auto &o = overrides[candidate];
o.timestamp = timestamp; o.timestamp = timestamp;
o.count++; o.count++;
} }
static double Score(size_t eventCount, static double Score(size_t eventCount, size_t totalCount, double eventTimestamp, double timestamp, double lambda)
size_t totalCount, {
double eventTimestamp,
double timestamp,
double lambda) {
double decay = exp((timestamp - eventTimestamp) * lambda); double decay = exp((timestamp - eventTimestamp) * lambda);
if (decay < DecayThreshould) { if (decay < DecayThreshould)
{
return 0.0; return 0.0;
} }
@ -134,29 +135,31 @@ static double Score(size_t eventCount,
return prob * decay; return prob * decay;
} }
static bool IsEndingPunctuation(const std::string& value) { static bool IsEndingPunctuation(const std::string &value)
return value == "" || value == "" || value== "" || value == "" || {
value == "" || value == "" || value== "" || value == ""; return value == "" || value == "" || value == "" || value == "" || value == "" || value == "" ||
value == "" || value == "";
} }
static std::string WalkedNodesToKey(const std::vector<Gramambular::NodeAnchor>& walkedNodes, static std::string WalkedNodesToKey(const std::vector<Gramambular::NodeAnchor> &walkedNodes, size_t cursorIndex)
size_t cursorIndex) { {
std::stringstream s; std::stringstream s;
std::vector<Gramambular::NodeAnchor> n; std::vector<Gramambular::NodeAnchor> n;
size_t ll = 0; size_t ll = 0;
for (std::vector<Gramambular::NodeAnchor>::const_iterator i = walkedNodes.begin(); for (std::vector<Gramambular::NodeAnchor>::const_iterator i = walkedNodes.begin(); i != walkedNodes.end(); ++i)
i != walkedNodes.end(); {
++i) { const auto &nn = *i;
const auto& nn = *i;
n.push_back(nn); n.push_back(nn);
ll += nn.spanningLength; ll += nn.spanningLength;
if (ll >= cursorIndex) { if (ll >= cursorIndex)
{
break; break;
} }
} }
std::vector<Gramambular::NodeAnchor>::const_reverse_iterator r = n.rbegin(); std::vector<Gramambular::NodeAnchor>::const_reverse_iterator r = n.rbegin();
if (r == n.rend()) { if (r == n.rend())
{
return ""; return "";
} }
@ -165,40 +168,44 @@ static std::string WalkedNodesToKey(const std::vector<Gramambular::NodeAnchor>&
s.clear(); s.clear();
s.str(std::string()); s.str(std::string());
if (r != n.rend()) { if (r != n.rend())
{
std::string value = (*r).node->currentKeyValue().value; std::string value = (*r).node->currentKeyValue().value;
if (IsEndingPunctuation(value)) { if (IsEndingPunctuation(value))
{
s << "()"; s << "()";
r = n.rend(); r = n.rend();
} else { }
s << "(" else
<< (*r).node->currentKeyValue().key {
<< "," s << "(" << (*r).node->currentKeyValue().key << "," << value << ")";
<< value
<< ")";
++r; ++r;
} }
} else { }
else
{
s << "()"; s << "()";
} }
std::string prev = s.str(); std::string prev = s.str();
s.clear(); s.clear();
s.str(std::string()); s.str(std::string());
if (r != n.rend()) { if (r != n.rend())
{
std::string value = (*r).node->currentKeyValue().value; std::string value = (*r).node->currentKeyValue().value;
if (IsEndingPunctuation(value)) { if (IsEndingPunctuation(value))
{
s << "()"; s << "()";
r = n.rend(); r = n.rend();
} else { }
s << "(" else
<< (*r).node->currentKeyValue().key {
<< "," s << "(" << (*r).node->currentKeyValue().key << "," << value << ")";
<< value
<< ")";
++r; ++r;
} }
} else { }
else
{
s << "()"; s << "()";
} }
std::string anterior = s.str(); std::string anterior = s.str();

View File

@ -1,20 +1,27 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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 #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" #include "Gramambular.h"
namespace vChewing { namespace vChewing
{
using namespace Gramambular; using namespace Gramambular;
class UserOverrideModel { class UserOverrideModel
public: {
public:
UserOverrideModel(size_t capacity, double decayConstant); UserOverrideModel(size_t capacity, double decayConstant);
void observe(const std::vector<Gramambular::NodeAnchor>& walkedNodes, void observe(const std::vector<Gramambular::NodeAnchor> &walkedNodes, size_t cursorIndex,
size_t cursorIndex, const std::string &candidate, double timestamp);
const std::string& candidate,
double timestamp);
std::string suggest(const std::vector<Gramambular::NodeAnchor>& walkedNodes, std::string suggest(const std::vector<Gramambular::NodeAnchor> &walkedNodes, size_t cursorIndex, double timestamp);
size_t cursorIndex,
double timestamp);
private: private:
struct Override { struct Override
{
size_t count; size_t count;
double timestamp; double timestamp;
Override() : count(0), timestamp(0.0) {} Override() : count(0), timestamp(0.0)
{
}
}; };
struct Observation { struct Observation
{
size_t count; size_t count;
std::map<std::string, Override> overrides; std::map<std::string, Override> overrides;
Observation() : count(0) {} Observation() : count(0)
void update(const std::string& candidate, double timestamp); {
}
void update(const std::string &candidate, double timestamp);
}; };
typedef std::pair<std::string, Observation> KeyObservationPair; typedef std::pair<std::string, Observation> KeyObservationPair;
@ -69,4 +80,3 @@ private:
}; // namespace vChewing }; // namespace vChewing
#endif #endif

View File

@ -1,35 +1,43 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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 #ifndef USERPHRASESLM_H
#define USERPHRASESLM_H #define USERPHRASESLM_H
#include <string>
#include <map>
#include <iostream>
#include "LanguageModel.h" #include "LanguageModel.h"
#include <iostream>
#include <map>
#include <string>
namespace vChewing { namespace vChewing
{
class UserPhrasesLM : public Gramambular::LanguageModel class UserPhrasesLM : public Gramambular::LanguageModel
{ {
public: public:
UserPhrasesLM(); UserPhrasesLM();
~UserPhrasesLM(); ~UserPhrasesLM();
@ -38,31 +46,37 @@ public:
void close(); void close();
void dump(); void dump();
virtual bool allowConsolidation() { virtual bool allowConsolidation()
{
return true; return true;
} }
virtual float overridedValue() { virtual float overridedValue()
{
return 0.0; return 0.0;
} }
virtual const std::vector<Gramambular::Bigram> bigramsForKeys(const std::string& preceedingKey, const std::string& key); virtual const std::vector<Gramambular::Bigram> bigramsForKeys(const std::string &preceedingKey,
virtual const std::vector<Gramambular::Unigram> unigramsForKey(const std::string& key); const std::string &key);
virtual bool hasUnigramsForKey(const std::string& key); virtual const std::vector<Gramambular::Unigram> unigramsForKey(const std::string &key);
virtual bool hasUnigramsForKey(const std::string &key);
protected:
struct Row { protected:
Row(std::string_view& k, std::string_view& v) : key(k), value(v) {} struct Row
{
Row(std::string_view &k, std::string_view &v) : key(k), value(v)
{
}
std::string_view key; std::string_view key;
std::string_view value; std::string_view value;
}; };
std::map<std::string_view, std::vector<Row>> keyRowMap; std::map<std::string_view, std::vector<Row>> keyRowMap;
int fd; int fd;
void *data; void *data;
size_t length; size_t length;
}; };
} } // namespace vChewing
#endif #endif

View File

@ -1,53 +1,60 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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 "UserPhrasesLM.h"
#include "vChewing-Swift.h" #include "vChewing-Swift.h"
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h> #include <fcntl.h>
#include <fstream> #include <fstream>
#include <unistd.h> #include <sys/mman.h>
#include <sys/stat.h>
#include <syslog.h> #include <syslog.h>
#include <unistd.h>
#include "KeyValueBlobReader.h" #include "KeyValueBlobReader.h"
#include "LMConsolidator.h" #include "LMConsolidator.h"
namespace vChewing { namespace vChewing
{
UserPhrasesLM::UserPhrasesLM() UserPhrasesLM::UserPhrasesLM() : fd(-1), data(0), length(0)
: fd(-1)
, data(0)
, length(0)
{ {
} }
UserPhrasesLM::~UserPhrasesLM() UserPhrasesLM::~UserPhrasesLM()
{ {
if (data) { if (data)
{
close(); close();
} }
} }
bool UserPhrasesLM::isLoaded() bool UserPhrasesLM::isLoaded()
{ {
if (data) { if (data)
{
return true; return true;
} }
return false; return false;
@ -55,23 +62,27 @@ bool UserPhrasesLM::isLoaded()
bool UserPhrasesLM::open(const char *path) bool UserPhrasesLM::open(const char *path)
{ {
if (data) { if (data)
{
return false; return false;
} }
if (allowConsolidation()) { if (allowConsolidation())
{
LMConsolidator::FixEOF(path); LMConsolidator::FixEOF(path);
LMConsolidator::ConsolidateContent(path, true); LMConsolidator::ConsolidateContent(path, true);
} }
fd = ::open(path, O_RDONLY); fd = ::open(path, O_RDONLY);
if (fd == -1) { if (fd == -1)
{
printf("open:: file not exist"); printf("open:: file not exist");
return false; return false;
} }
struct stat sb; struct stat sb;
if (fstat(fd, &sb) == -1) { if (fstat(fd, &sb) == -1)
{
printf("open:: cannot open file"); printf("open:: cannot open file");
return false; return false;
} }
@ -79,22 +90,27 @@ bool UserPhrasesLM::open(const char *path)
length = (size_t)sb.st_size; length = (size_t)sb.st_size;
data = mmap(NULL, length, PROT_READ, MAP_SHARED, fd, 0); data = mmap(NULL, length, PROT_READ, MAP_SHARED, fd, 0);
if (!data) { if (!data)
{
::close(fd); ::close(fd);
return false; return false;
} }
KeyValueBlobReader reader(static_cast<char*>(data), length); KeyValueBlobReader reader(static_cast<char *>(data), length);
KeyValueBlobReader::KeyValue keyValue; KeyValueBlobReader::KeyValue keyValue;
KeyValueBlobReader::State state; KeyValueBlobReader::State state;
while ((state = reader.Next(&keyValue)) == KeyValueBlobReader::State::HAS_PAIR) { 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. {
// 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); keyRowMap[keyValue.value].emplace_back(keyValue.value, keyValue.key);
} }
// 下面這一段或許可以做成開關、來詢問是否對使用者語彙採取寬鬆策略(哪怕有行內容寫錯也會放行) // 下面這一段或許可以做成開關、來詢問是否對使用者語彙採取寬鬆策略(哪怕有行內容寫錯也會放行)
if (state == KeyValueBlobReader::State::ERROR) { if (state == KeyValueBlobReader::State::ERROR)
{
// close(); // 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 false;
} }
return true; return true;
@ -102,7 +118,8 @@ bool UserPhrasesLM::open(const char *path)
void UserPhrasesLM::close() void UserPhrasesLM::close()
{ {
if (data) { if (data)
{
munmap(data, length); munmap(data, length);
::close(fd); ::close(fd);
data = 0; data = 0;
@ -113,26 +130,31 @@ void UserPhrasesLM::close()
void UserPhrasesLM::dump() void UserPhrasesLM::dump()
{ {
for (const auto& entry : keyRowMap) { for (const auto &entry : keyRowMap)
const std::vector<Row>& rows = entry.second; {
for (const auto& row : rows) { const std::vector<Row> &rows = entry.second;
for (const auto &row : rows)
{
std::cerr << row.key << " " << row.value << "\n"; std::cerr << row.key << " " << row.value << "\n";
} }
} }
} }
const std::vector<Gramambular::Bigram> UserPhrasesLM::bigramsForKeys(const std::string& preceedingKey, const std::string& key) const std::vector<Gramambular::Bigram> UserPhrasesLM::bigramsForKeys(const std::string &preceedingKey,
const std::string &key)
{ {
return std::vector<Gramambular::Bigram>(); return std::vector<Gramambular::Bigram>();
} }
const std::vector<Gramambular::Unigram> UserPhrasesLM::unigramsForKey(const std::string& key) const std::vector<Gramambular::Unigram> UserPhrasesLM::unigramsForKey(const std::string &key)
{ {
std::vector<Gramambular::Unigram> v; std::vector<Gramambular::Unigram> v;
auto iter = keyRowMap.find(key); auto iter = keyRowMap.find(key);
if (iter != keyRowMap.end()) { if (iter != keyRowMap.end())
const std::vector<Row>& rows = iter->second; {
for (const auto& row : rows) { const std::vector<Row> &rows = iter->second;
for (const auto &row : rows)
{
Gramambular::Unigram g; Gramambular::Unigram g;
g.keyValue.key = row.key; g.keyValue.key = row.key;
g.keyValue.value = row.value; g.keyValue.value = row.value;
@ -144,9 +166,9 @@ const std::vector<Gramambular::Unigram> UserPhrasesLM::unigramsForKey(const std:
return v; return v;
} }
bool UserPhrasesLM::hasUnigramsForKey(const std::string& key) bool UserPhrasesLM::hasUnigramsForKey(const std::string &key)
{ {
return keyRowMap.find(key) != keyRowMap.end(); return keyRowMap.find(key) != keyRowMap.end();
} }
}; // namespace vChewing }; // namespace vChewing

View File

@ -1,24 +1,31 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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/Foundation.h>
#import "KeyHandler.h" #import "KeyHandler.h"
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
@ -33,8 +40,13 @@ NS_ASSUME_NONNULL_BEGIN
+ (BOOL)checkIfSpecifiedUserDataFolderValid:(NSString *)folderPath; + (BOOL)checkIfSpecifiedUserDataFolderValid:(NSString *)folderPath;
+ (NSString *)dataFolderPath:(bool)isDefaultFolder NS_SWIFT_NAME(dataFolderPath(isDefaultFolder:)); + (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)checkIfUserPhraseExist:(NSString *)userPhrase
+ (BOOL)writeUserPhrase:(NSString *)userPhrase inputMode:(InputMode)mode areWeDuplicating:(BOOL)areWeDuplicating areWeDeleting:(BOOL)areWeDeleting; 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)setPhraseReplacementEnabled:(BOOL)phraseReplacementEnabled;
+ (void)setCNSEnabled:(BOOL)cnsEnabled; + (void)setCNSEnabled:(BOOL)cnsEnabled;
+ (void)setSymbolEnabled:(BOOL)symbolEnabled; + (void)setSymbolEnabled:(BOOL)symbolEnabled;

View File

@ -1,26 +1,33 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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 "mgrLangModel.h"
#import "LMConsolidator.h"
#import "mgrLangModel_Privates.h" #import "mgrLangModel_Privates.h"
#import "vChewing-Swift.h" #import "vChewing-Swift.h"
#import "LMConsolidator.h"
static const int kUserOverrideModelCapacity = 500; static const int kUserOverrideModelCapacity = 500;
static const double kObservedOverrideHalflife = 5400.0; static const double kObservedOverrideHalflife = 5400.0;
@ -54,70 +61,90 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing
+ (void)loadDataModels + (void)loadDataModels
{ {
if (!gLangModelCHT.isDataModelLoaded()) { if (!gLangModelCHT.isDataModelLoaded())
{
LTLoadLanguageModelFile(@"data-cht", gLangModelCHT); LTLoadLanguageModelFile(@"data-cht", gLangModelCHT);
} }
if (!gLangModelCHT.isMiscDataLoaded()) { if (!gLangModelCHT.isMiscDataLoaded())
gLangModelCHT.loadMiscData([[self specifyBundleDataPath: @"data-zhuyinwen"] UTF8String]); {
gLangModelCHT.loadMiscData([[self specifyBundleDataPath:@"data-zhuyinwen"] UTF8String]);
} }
if (!gLangModelCHT.isSymbolDataLoaded()){ if (!gLangModelCHT.isSymbolDataLoaded())
gLangModelCHT.loadSymbolData([[self specifyBundleDataPath: @"data-symbols"] UTF8String]); {
gLangModelCHT.loadSymbolData([[self specifyBundleDataPath:@"data-symbols"] UTF8String]);
} }
if (!gLangModelCHT.isCNSDataLoaded()){ if (!gLangModelCHT.isCNSDataLoaded())
gLangModelCHT.loadCNSData([[self specifyBundleDataPath: @"char-kanji-cns"] UTF8String]); {
gLangModelCHT.loadCNSData([[self specifyBundleDataPath:@"char-kanji-cns"] UTF8String]);
} }
// ----------------- // -----------------
if (!gLangModelCHS.isDataModelLoaded()) { if (!gLangModelCHS.isDataModelLoaded())
{
LTLoadLanguageModelFile(@"data-chs", gLangModelCHS); LTLoadLanguageModelFile(@"data-chs", gLangModelCHS);
} }
if (!gLangModelCHS.isMiscDataLoaded()) { if (!gLangModelCHS.isMiscDataLoaded())
gLangModelCHS.loadMiscData([[self specifyBundleDataPath: @"data-zhuyinwen"] UTF8String]); {
gLangModelCHS.loadMiscData([[self specifyBundleDataPath:@"data-zhuyinwen"] UTF8String]);
} }
if (!gLangModelCHS.isSymbolDataLoaded()){ if (!gLangModelCHS.isSymbolDataLoaded())
gLangModelCHS.loadSymbolData([[self specifyBundleDataPath: @"data-symbols"] UTF8String]); {
gLangModelCHS.loadSymbolData([[self specifyBundleDataPath:@"data-symbols"] UTF8String]);
} }
if (!gLangModelCHS.isCNSDataLoaded()){ if (!gLangModelCHS.isCNSDataLoaded())
gLangModelCHS.loadCNSData([[self specifyBundleDataPath: @"char-kanji-cns"] UTF8String]); {
gLangModelCHS.loadCNSData([[self specifyBundleDataPath:@"char-kanji-cns"] UTF8String]);
} }
} }
+ (void)loadDataModel:(InputMode)mode + (void)loadDataModel:(InputMode)mode
{ {
if ([mode isEqualToString:imeModeCHT]) { if ([mode isEqualToString:imeModeCHT])
if (!gLangModelCHT.isDataModelLoaded()) { {
if (!gLangModelCHT.isDataModelLoaded())
{
LTLoadLanguageModelFile(@"data-cht", gLangModelCHT); LTLoadLanguageModelFile(@"data-cht", gLangModelCHT);
} }
if (!gLangModelCHT.isMiscDataLoaded()) { if (!gLangModelCHT.isMiscDataLoaded())
gLangModelCHT.loadMiscData([[self specifyBundleDataPath: @"data-zhuyinwen"] UTF8String]); {
gLangModelCHT.loadMiscData([[self specifyBundleDataPath:@"data-zhuyinwen"] UTF8String]);
} }
if (!gLangModelCHT.isSymbolDataLoaded()){ if (!gLangModelCHT.isSymbolDataLoaded())
gLangModelCHT.loadSymbolData([[self specifyBundleDataPath: @"data-symbols"] UTF8String]); {
gLangModelCHT.loadSymbolData([[self specifyBundleDataPath:@"data-symbols"] UTF8String]);
} }
if (!gLangModelCHT.isCNSDataLoaded()){ if (!gLangModelCHT.isCNSDataLoaded())
gLangModelCHT.loadCNSData([[self specifyBundleDataPath: @"char-kanji-cns"] UTF8String]); {
gLangModelCHT.loadCNSData([[self specifyBundleDataPath:@"char-kanji-cns"] UTF8String]);
} }
} }
if ([mode isEqualToString:imeModeCHS]) { if ([mode isEqualToString:imeModeCHS])
if (!gLangModelCHS.isDataModelLoaded()) { {
if (!gLangModelCHS.isDataModelLoaded())
{
LTLoadLanguageModelFile(@"data-chs", gLangModelCHS); LTLoadLanguageModelFile(@"data-chs", gLangModelCHS);
} }
if (!gLangModelCHS.isMiscDataLoaded()) { if (!gLangModelCHS.isMiscDataLoaded())
gLangModelCHS.loadMiscData([[self specifyBundleDataPath: @"data-zhuyinwen"] UTF8String]); {
gLangModelCHS.loadMiscData([[self specifyBundleDataPath:@"data-zhuyinwen"] UTF8String]);
} }
if (!gLangModelCHS.isSymbolDataLoaded()){ if (!gLangModelCHS.isSymbolDataLoaded())
gLangModelCHS.loadSymbolData([[self specifyBundleDataPath: @"data-symbols"] UTF8String]); {
gLangModelCHS.loadSymbolData([[self specifyBundleDataPath:@"data-symbols"] UTF8String]);
} }
if (!gLangModelCHS.isCNSDataLoaded()){ if (!gLangModelCHS.isCNSDataLoaded())
gLangModelCHS.loadCNSData([[self specifyBundleDataPath: @"char-kanji-cns"] UTF8String]); {
gLangModelCHS.loadCNSData([[self specifyBundleDataPath:@"char-kanji-cns"] UTF8String]);
} }
} }
} }
+ (void)loadUserPhrases + (void)loadUserPhrases
{ {
gLangModelCHT.loadUserPhrases([[self userPhrasesDataPath:imeModeCHT] UTF8String], [[self excludedPhrasesDataPath:imeModeCHT] UTF8String]); gLangModelCHT.loadUserPhrases([[self userPhrasesDataPath:imeModeCHT] UTF8String],
gLangModelCHS.loadUserPhrases([[self userPhrasesDataPath:imeModeCHS] UTF8String], [[self excludedPhrasesDataPath:imeModeCHS] UTF8String]); [[self excludedPhrasesDataPath:imeModeCHT] UTF8String]);
gLangModelCHS.loadUserPhrases([[self userPhrasesDataPath:imeModeCHS] UTF8String],
[[self excludedPhrasesDataPath:imeModeCHS] UTF8String]);
gLangModelCHT.loadUserSymbolData([[self userSymbolDataPath:imeModeCHT] UTF8String]); gLangModelCHT.loadUserSymbolData([[self userSymbolDataPath:imeModeCHT] UTF8String]);
gLangModelCHS.loadUserSymbolData([[self userSymbolDataPath:imeModeCHS] UTF8String]); gLangModelCHS.loadUserSymbolData([[self userSymbolDataPath:imeModeCHS] UTF8String]);
} }
@ -139,19 +166,26 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing
NSString *folderPath = [self dataFolderPath:false]; NSString *folderPath = [self dataFolderPath:false];
BOOL isFolder = NO; BOOL isFolder = NO;
BOOL folderExist = [[NSFileManager defaultManager] fileExistsAtPath:folderPath isDirectory:&isFolder]; BOOL folderExist = [[NSFileManager defaultManager] fileExistsAtPath:folderPath isDirectory:&isFolder];
if (folderExist && !isFolder) { if (folderExist && !isFolder)
{
NSError *error = nil; NSError *error = nil;
[[NSFileManager defaultManager] removeItemAtPath:folderPath error:&error]; [[NSFileManager defaultManager] removeItemAtPath:folderPath error:&error];
if (error) { if (error)
{
NSLog(@"Failed to remove folder %@", error); NSLog(@"Failed to remove folder %@", error);
return NO; return NO;
} }
folderExist = NO; folderExist = NO;
} }
if (!folderExist) { if (!folderExist)
{
NSError *error = nil; NSError *error = nil;
[[NSFileManager defaultManager] createDirectoryAtPath:folderPath withIntermediateDirectories:YES attributes:nil error:&error]; [[NSFileManager defaultManager] createDirectoryAtPath:folderPath
if (error) { withIntermediateDirectories:YES
attributes:nil
error:&error];
if (error)
{
NSLog(@"Failed to create folder %@", error); NSLog(@"Failed to create folder %@", error);
return NO; return NO;
} }
@ -163,26 +197,34 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing
{ {
BOOL isFolder = NO; BOOL isFolder = NO;
BOOL folderExist = [[NSFileManager defaultManager] fileExistsAtPath:folderPath isDirectory:&isFolder]; BOOL folderExist = [[NSFileManager defaultManager] fileExistsAtPath:folderPath isDirectory:&isFolder];
if ((folderExist && !isFolder) || (!folderExist)) { if ((folderExist && !isFolder) || (!folderExist))
{
return NO; return NO;
} }
return YES; 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]; NSURL *templateURL = [[NSBundle mainBundle] URLForResource:templateBasename withExtension:ext];
NSData *templateData; NSData *templateData;
if (templateURL) { if (templateURL)
{
templateData = [NSData dataWithContentsOfURL:templateURL]; templateData = [NSData dataWithContentsOfURL:templateURL];
} else { }
else
{
templateData = [@"" dataUsingEncoding:NSUTF8StringEncoding]; templateData = [@"" dataUsingEncoding:NSUTF8StringEncoding];
} }
BOOL result = [templateData writeToFile:filePath atomically:YES]; BOOL result = [templateData writeToFile:filePath atomically:YES];
if (!result) { if (!result)
{
NSLog(@"Failed to write file"); NSLog(@"Failed to write file");
return NO; return NO;
} }
@ -192,36 +234,76 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing
+ (BOOL)checkIfUserLanguageModelFilesExist + (BOOL)checkIfUserLanguageModelFilesExist
{ {
if (![self checkIfUserDataFolderExists]) return NO; if (![self checkIfUserDataFolderExists])
if (![self ensureFileExists:[self userPhrasesDataPath:imeModeCHS] populateWithTemplate:kUserDataTemplateName extension:kTemplateExtension]) return NO; return NO;
if (![self ensureFileExists:[self userPhrasesDataPath:imeModeCHT] populateWithTemplate:kUserDataTemplateName extension:kTemplateExtension]) return NO; if (![self ensureFileExists:[self userPhrasesDataPath:imeModeCHS]
if (![self ensureFileExists:[self userAssociatedPhrasesDataPath:imeModeCHS] populateWithTemplate:kUserAssDataTemplateName extension:kTemplateExtension]) return NO; populateWithTemplate:kUserDataTemplateName
if (![self ensureFileExists:[self userAssociatedPhrasesDataPath:imeModeCHT] populateWithTemplate:kUserAssDataTemplateName extension:kTemplateExtension]) return NO; extension:kTemplateExtension])
if (![self ensureFileExists:[self excludedPhrasesDataPath:imeModeCHS] populateWithTemplate:kExcludedPhrasesvChewingTemplateName extension:kTemplateExtension]) return NO; return NO;
if (![self ensureFileExists:[self excludedPhrasesDataPath:imeModeCHT] populateWithTemplate:kExcludedPhrasesvChewingTemplateName extension:kTemplateExtension]) return NO; if (![self ensureFileExists:[self userPhrasesDataPath:imeModeCHT]
if (![self ensureFileExists:[self phraseReplacementDataPath:imeModeCHS] populateWithTemplate:kPhraseReplacementTemplateName extension:kTemplateExtension]) return NO; populateWithTemplate:kUserDataTemplateName
if (![self ensureFileExists:[self phraseReplacementDataPath:imeModeCHT] populateWithTemplate:kPhraseReplacementTemplateName extension:kTemplateExtension]) return NO; extension:kTemplateExtension])
if (![self ensureFileExists:[self userSymbolDataPath:imeModeCHT] populateWithTemplate:kUserSymbolDataTemplateName extension:kTemplateExtension]) return NO; return NO;
if (![self ensureFileExists:[self userSymbolDataPath:imeModeCHS] populateWithTemplate:kUserSymbolDataTemplateName 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; 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); string unigramKey = string(key.UTF8String);
vector<vChewing::Unigram> unigrams = [mode isEqualToString:imeModeCHT] ? gLangModelCHT.unigramsForKey(unigramKey): gLangModelCHS.unigramsForKey(unigramKey); vector<vChewing::Unigram> unigrams = [mode isEqualToString:imeModeCHT] ? gLangModelCHT.unigramsForKey(unigramKey)
: gLangModelCHS.unigramsForKey(unigramKey);
string userPhraseString = string(userPhrase.UTF8String); string userPhraseString = string(userPhrase.UTF8String);
for (auto unigram: unigrams) { for (auto unigram : unigrams)
if (unigram.keyValue.value == userPhraseString) { {
if (unigram.keyValue.value == userPhraseString)
{
return YES; return YES;
} }
} }
return NO; 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; return NO;
} }
@ -233,15 +315,17 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing
// [currentMarkedPhrase appendString:@"\n"]; // [currentMarkedPhrase appendString:@"\n"];
// } // }
[currentMarkedPhrase appendString:userPhrase]; [currentMarkedPhrase appendString:userPhrase];
if (areWeDuplicating && !areWeDeleting) { if (areWeDuplicating && !areWeDeleting)
{
// Do not use ASCII characters to comment here. // 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:@"\t#𝙾𝚟𝚎𝚛𝚛𝚒𝚍𝚎"];
} }
[currentMarkedPhrase appendString:@"\n"]; [currentMarkedPhrase appendString:@"\n"];
NSFileHandle *writeFile = [NSFileHandle fileHandleForUpdatingAtPath:path]; NSFileHandle *writeFile = [NSFileHandle fileHandleForUpdatingAtPath:path];
if (!writeFile) { if (!writeFile)
{
return NO; return NO;
} }
[writeFile seekToEndOfFile]; [writeFile seekToEndOfFile];
@ -249,12 +333,14 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing
[writeFile writeData:data]; [writeFile writeData:data];
[writeFile closeFile]; [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); vChewing::LMConsolidator::ConsolidateContent([path UTF8String], false);
// We use FSEventStream to monitor the change of the user phrase folder, // 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. // so we don't have to load data here unless FSEventStream is disabled by user.
if (!mgrPrefs.shouldAutoReloadUserDataFiles) { if (!mgrPrefs.shouldAutoReloadUserDataFiles)
{
[self loadUserPhrases]; [self loadUserPhrases];
} }
return YES; return YES;
@ -263,15 +349,21 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing
+ (NSString *)dataFolderPath:(bool)isDefaultFolder + (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; NSString *userDictPath = [appSupportPath stringByAppendingPathComponent:@"vChewing"].stringByExpandingTildeInPath;
if (mgrPrefs.userDataFolderSpecified.stringByExpandingTildeInPath == userDictPath || isDefaultFolder) { if (mgrPrefs.userDataFolderSpecified.stringByExpandingTildeInPath == userDictPath || isDefaultFolder)
{
return userDictPath; return userDictPath;
} }
if ([mgrPrefs ifSpecifiedUserDataPathExistsInPlist]) { if ([mgrPrefs ifSpecifiedUserDataPathExistsInPlist])
if ([self checkIfSpecifiedUserDataFolderValid:mgrPrefs.userDataFolderSpecified.stringByExpandingTildeInPath]) { {
if ([self checkIfSpecifiedUserDataFolderValid:mgrPrefs.userDataFolderSpecified.stringByExpandingTildeInPath])
{
return mgrPrefs.userDataFolderSpecified.stringByExpandingTildeInPath; return mgrPrefs.userDataFolderSpecified.stringByExpandingTildeInPath;
} else { }
else
{
[NSUserDefaults.standardUserDefaults removeObjectForKey:@"UserDataFolderSpecified"]; [NSUserDefaults.standardUserDefaults removeObjectForKey:@"UserDataFolderSpecified"];
} }
} }
@ -286,13 +378,15 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing
+ (NSString *)userSymbolDataPath:(InputMode)mode; + (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]; return [[self dataFolderPath:false] stringByAppendingPathComponent:fileName];
} }
+ (NSString *)userAssociatedPhrasesDataPath:(InputMode)mode; + (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]; return [[self dataFolderPath:false] stringByAppendingPathComponent:fileName];
} }
@ -304,11 +398,12 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing
+ (NSString *)phraseReplacementDataPath:(InputMode)mode; + (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]; return [[self dataFolderPath:false] stringByAppendingPathComponent:fileName];
} }
+ (vChewing::LMInstantiator *)lmCHT + (vChewing::LMInstantiator *)lmCHT
{ {
return &gLangModelCHT; return &gLangModelCHT;
} }

View File

@ -1,33 +1,40 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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 "LMInstantiator.h"
#import "UserOverrideModel.h"
#import "mgrLangModel.h"
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
@interface mgrLangModel () @interface mgrLangModel ()
@property (class, readonly, nonatomic) vChewing::LMInstantiator *lmCHT; @property(class, readonly, nonatomic) vChewing::LMInstantiator *lmCHT;
@property (class, readonly, nonatomic) vChewing::LMInstantiator *lmCHS; @property(class, readonly, nonatomic) vChewing::LMInstantiator *lmCHS;
@property (class, readonly, nonatomic) vChewing::UserOverrideModel *userOverrideModelCHS; @property(class, readonly, nonatomic) vChewing::UserOverrideModel *userOverrideModelCHS;
@property (class, readonly, nonatomic) vChewing::UserOverrideModel *userOverrideModelCHT; @property(class, readonly, nonatomic) vChewing::UserOverrideModel *userOverrideModelCHT;
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

View File

@ -1,20 +1,27 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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_ #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" #include "KeyValuePair.h"
namespace Gramambular { namespace Gramambular
class Bigram { {
public: class Bigram
{
public:
Bigram(); Bigram();
KeyValuePair preceedingKeyValue; KeyValuePair preceedingKeyValue;
KeyValuePair keyValue; KeyValuePair keyValue;
double score; 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(); std::streamsize p = stream.precision();
stream.precision(6); stream.precision(6);
stream << "(" << gram.keyValue << "|" << gram.preceedingKeyValue << "," stream << "(" << gram.keyValue << "|" << gram.preceedingKeyValue << "," << gram.score << ")";
<< gram.score << ")";
stream.precision(p); stream.precision(p);
return stream; return stream;
} }
inline std::ostream& operator<<(std::ostream& stream, inline std::ostream &operator<<(std::ostream &stream, const std::vector<Bigram> &grams)
const std::vector<Bigram>& grams) { {
stream << "[" << grams.size() << "]=>{"; stream << "[" << grams.size() << "]=>{";
size_t index = 0; size_t index = 0;
for (std::vector<Bigram>::const_iterator gi = grams.begin(); for (std::vector<Bigram>::const_iterator gi = grams.begin(); gi != grams.end(); ++gi, ++index)
gi != grams.end(); ++gi, ++index) { {
stream << index << "=>"; stream << index << "=>";
stream << *gi; stream << *gi;
if (gi + 1 != grams.end()) { if (gi + 1 != grams.end())
{
stream << ","; stream << ",";
} }
} }
stream << "}"; stream << "}";
return stream; return stream;
} }
inline Bigram::Bigram() : score(0.0) {} 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 bool Bigram::operator<(const Bigram& another) const { inline bool Bigram::operator==(const Bigram &another) const
if (preceedingKeyValue < another.preceedingKeyValue) { {
return preceedingKeyValue == another.preceedingKeyValue && keyValue == another.keyValue && score == another.score;
}
inline bool Bigram::operator<(const Bigram &another) const
{
if (preceedingKeyValue < another.preceedingKeyValue)
{
return true; return true;
} else if (preceedingKeyValue == another.preceedingKeyValue) { }
if (keyValue < another.keyValue) { else if (preceedingKeyValue == another.preceedingKeyValue)
{
if (keyValue < another.keyValue)
{
return true; return true;
} else if (keyValue == another.keyValue) { }
else if (keyValue == another.keyValue)
{
return score < another.score; return score < another.score;
} }
return false; return false;
} }
return false; return false;
} }
} // namespace Gramambular } // namespace Gramambular
#endif #endif

View File

@ -1,20 +1,27 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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_ #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 "Grid.h"
#include "LanguageModel.h" #include "LanguageModel.h"
namespace Gramambular { namespace Gramambular
{
class BlockReadingBuilder { class BlockReadingBuilder
public: {
explicit BlockReadingBuilder(LanguageModel* lm); public:
explicit BlockReadingBuilder(LanguageModel *lm);
void clear(); void clear();
size_t length() const; size_t length() const;
size_t cursorIndex() const; size_t cursorIndex() const;
void setCursorIndex(size_t newIndex); void setCursorIndex(size_t newIndex);
void insertReadingAtCursor(const std::string& reading); void insertReadingAtCursor(const std::string &reading);
bool deleteReadingBeforeCursor(); // backspace bool deleteReadingBeforeCursor(); // backspace
bool deleteReadingAfterCursor(); // delete bool deleteReadingAfterCursor(); // delete
bool removeHeadReadings(size_t count); bool removeHeadReadings(size_t count);
void setJoinSeparator(const std::string& separator); void setJoinSeparator(const std::string &separator);
const std::string joinSeparator() const; const std::string joinSeparator() const;
std::vector<std::string> readings() const; std::vector<std::string> readings() const;
Grid& grid(); Grid &grid();
protected: protected:
void build(); void build();
static const std::string Join(std::vector<std::string>::const_iterator begin, static const std::string Join(std::vector<std::string>::const_iterator begin,
std::vector<std::string>::const_iterator end, std::vector<std::string>::const_iterator end, const std::string &separator);
const std::string& separator);
// 規定最多可以組成的詞的字數上限為 10 // 規定最多可以組成的詞的字數上限為 10
static const size_t MaximumBuildSpanLength = 10; static const size_t MaximumBuildSpanLength = 10;
size_t m_cursorIndex; size_t m_cursorIndex;
std::vector<std::string> m_readings; std::vector<std::string> m_readings;
Grid m_grid; Grid m_grid;
LanguageModel* m_LM; LanguageModel *m_LM;
std::string m_joinSeparator; std::string m_joinSeparator;
}; };
inline BlockReadingBuilder::BlockReadingBuilder(LanguageModel* lm) inline BlockReadingBuilder::BlockReadingBuilder(LanguageModel *lm) : m_LM(lm), m_cursorIndex(0)
: m_LM(lm), m_cursorIndex(0) {} {
}
inline void BlockReadingBuilder::clear() { inline void BlockReadingBuilder::clear()
{
m_cursorIndex = 0; m_cursorIndex = 0;
m_readings.clear(); m_readings.clear();
m_grid.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; m_cursorIndex = newIndex > m_readings.size() ? m_readings.size() : newIndex;
} }
inline void BlockReadingBuilder::insertReadingAtCursor( inline void BlockReadingBuilder::insertReadingAtCursor(const std::string &reading)
const std::string& reading) { {
m_readings.insert(m_readings.begin() + m_cursorIndex, reading); m_readings.insert(m_readings.begin() + m_cursorIndex, reading);
m_grid.expandGridByOneAtLocation(m_cursorIndex); m_grid.expandGridByOneAtLocation(m_cursorIndex);
build(); build();
m_cursorIndex++; m_cursorIndex++;
} }
inline std::vector<std::string> BlockReadingBuilder::readings() const { inline std::vector<std::string> BlockReadingBuilder::readings() const
{
return m_readings; return m_readings;
} }
inline bool BlockReadingBuilder::deleteReadingBeforeCursor() { inline bool BlockReadingBuilder::deleteReadingBeforeCursor()
if (!m_cursorIndex) { {
if (!m_cursorIndex)
{
return false; return false;
} }
m_readings.erase(m_readings.begin() + m_cursorIndex - 1, m_readings.erase(m_readings.begin() + m_cursorIndex - 1, m_readings.begin() + m_cursorIndex);
m_readings.begin() + m_cursorIndex);
m_cursorIndex--; m_cursorIndex--;
m_grid.shrinkGridByOneAtLocation(m_cursorIndex); m_grid.shrinkGridByOneAtLocation(m_cursorIndex);
build(); build();
return true; return true;
} }
inline bool BlockReadingBuilder::deleteReadingAfterCursor() { inline bool BlockReadingBuilder::deleteReadingAfterCursor()
if (m_cursorIndex == m_readings.size()) { {
if (m_cursorIndex == m_readings.size())
{
return false; return false;
} }
m_readings.erase(m_readings.begin() + m_cursorIndex, m_readings.erase(m_readings.begin() + m_cursorIndex, m_readings.begin() + m_cursorIndex + 1);
m_readings.begin() + m_cursorIndex + 1);
m_grid.shrinkGridByOneAtLocation(m_cursorIndex); m_grid.shrinkGridByOneAtLocation(m_cursorIndex);
build(); build();
return true; return true;
} }
inline bool BlockReadingBuilder::removeHeadReadings(size_t count) { inline bool BlockReadingBuilder::removeHeadReadings(size_t count)
if (count > length()) { {
if (count > length())
{
return false; return false;
} }
for (size_t i = 0; i < count; i++) { for (size_t i = 0; i < count; i++)
if (m_cursorIndex) { {
if (m_cursorIndex)
{
m_cursorIndex--; m_cursorIndex--;
} }
m_readings.erase(m_readings.begin(), m_readings.begin() + 1); m_readings.erase(m_readings.begin(), m_readings.begin() + 1);
m_grid.shrinkGridByOneAtLocation(0); m_grid.shrinkGridByOneAtLocation(0);
build(); build();
} }
return true; return true;
} }
inline void BlockReadingBuilder::setJoinSeparator( inline void BlockReadingBuilder::setJoinSeparator(const std::string &separator)
const std::string& separator) { {
m_joinSeparator = separator; m_joinSeparator = separator;
} }
inline const std::string BlockReadingBuilder::joinSeparator() const { inline const std::string BlockReadingBuilder::joinSeparator() const
{
return m_joinSeparator; return m_joinSeparator;
} }
inline Grid& BlockReadingBuilder::grid() { return m_grid; } inline Grid &BlockReadingBuilder::grid()
{
return m_grid;
}
inline void BlockReadingBuilder::build() { inline void BlockReadingBuilder::build()
if (!m_LM) { {
if (!m_LM)
{
return; return;
} }
size_t begin = 0; size_t begin = 0;
size_t end = m_cursorIndex + MaximumBuildSpanLength; size_t end = m_cursorIndex + MaximumBuildSpanLength;
if (m_cursorIndex < MaximumBuildSpanLength) { if (m_cursorIndex < MaximumBuildSpanLength)
{
begin = 0; begin = 0;
} else { }
else
{
begin = m_cursorIndex - MaximumBuildSpanLength; begin = m_cursorIndex - MaximumBuildSpanLength;
} }
if (end > m_readings.size()) { if (end > m_readings.size())
{
end = m_readings.size(); end = m_readings.size();
} }
for (size_t p = begin; p < end; p++) { for (size_t p = begin; p < end; p++)
for (size_t q = 1; q <= MaximumBuildSpanLength && p + q <= end; q++) { {
std::string combinedReading = Join( for (size_t q = 1; q <= MaximumBuildSpanLength && p + q <= end; q++)
m_readings.begin() + p, m_readings.begin() + p + q, m_joinSeparator); {
if (!m_grid.hasNodeAtLocationSpanningLengthMatchingKey(p, q, std::string combinedReading = Join(m_readings.begin() + p, m_readings.begin() + p + q, m_joinSeparator);
combinedReading)) { if (!m_grid.hasNodeAtLocationSpanningLengthMatchingKey(p, q, combinedReading))
{
std::vector<Unigram> unigrams = m_LM->unigramsForKey(combinedReading); std::vector<Unigram> unigrams = m_LM->unigramsForKey(combinedReading);
if (unigrams.size() > 0) { if (unigrams.size() > 0)
{
Node n(combinedReading, unigrams, std::vector<Bigram>()); Node n(combinedReading, unigrams, std::vector<Bigram>());
m_grid.insertNode(n, p, q); m_grid.insertNode(n, p, q);
} }
@ -185,21 +221,22 @@ inline void BlockReadingBuilder::build() {
} }
} }
inline const std::string BlockReadingBuilder::Join( inline const std::string BlockReadingBuilder::Join(std::vector<std::string>::const_iterator begin,
std::vector<std::string>::const_iterator begin,
std::vector<std::string>::const_iterator end, std::vector<std::string>::const_iterator end,
const std::string& separator) { const std::string &separator)
{
std::string result; std::string result;
for (std::vector<std::string>::const_iterator iter = begin; iter != end;) { for (std::vector<std::string>::const_iterator iter = begin; iter != end;)
{
result += *iter; result += *iter;
++iter; ++iter;
if (iter != end) { if (iter != end)
{
result += separator; result += separator;
} }
} }
return result; return result;
} }
} // namespace Gramambular } // namespace Gramambular
#endif #endif

View File

@ -1,20 +1,27 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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_ #ifndef GRAMAMBULAR_H_

View File

@ -1,20 +1,27 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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_ #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 "NodeAnchor.h"
#include "Span.h" #include "Span.h"
namespace Gramambular { namespace Gramambular
{
class Grid { class Grid
public: {
public:
void clear(); void clear();
void insertNode(const Node& node, size_t location, size_t spanningLength); void insertNode(const Node &node, size_t location, size_t spanningLength);
bool hasNodeAtLocationSpanningLengthMatchingKey(size_t location, bool hasNodeAtLocationSpanningLengthMatchingKey(size_t location, size_t spanningLength, const std::string &key);
size_t spanningLength,
const std::string& key);
void expandGridByOneAtLocation(size_t location); void expandGridByOneAtLocation(size_t location);
void shrinkGridByOneAtLocation(size_t location); void shrinkGridByOneAtLocation(size_t location);
size_t width() const; size_t width() const;
std::vector<NodeAnchor> nodesEndingAt(size_t location); std::vector<NodeAnchor> nodesEndingAt(size_t location);
std::vector<NodeAnchor> nodesCrossingOrEndingAt(size_t location); std::vector<NodeAnchor> nodesCrossingOrEndingAt(size_t location);
// "Freeze" the node with the unigram that represents the selected candidate // "Freeze" the node with the unigram that represents the selected candidate
// value. After this, the node that contains the unigram will always be // value. After this, the node that contains the unigram will always be
// evaluated to that unigram, while all other overlapping nodes will be reset // 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 // to their initial state (that is, if any of those nodes were "frozen" or
// fixed, they will be unfrozen.) // fixed, they will be unfrozen.)
NodeAnchor fixNodeSelectedCandidate(size_t location, NodeAnchor fixNodeSelectedCandidate(size_t location, const std::string &value);
const std::string& value);
// Similar to fixNodeSelectedCandidate, but instead of "freezing" the node, // Similar to fixNodeSelectedCandidate, but instead of "freezing" the node,
// only boost the unigram that represents the value with an overriding score. // only boost the unigram that represents the value with an overriding score.
// This has the same side effect as fixNodeSelectedCandidate, which is that // This has the same side effect as fixNodeSelectedCandidate, which is that
// all other overlapping nodes will be reset to their initial state. // all other overlapping nodes will be reset to their initial state.
void overrideNodeScoreForSelectedCandidate(size_t location, void overrideNodeScoreForSelectedCandidate(size_t location, const std::string &value, float overridingScore);
const std::string& value,
float overridingScore); std::string dumpDOT()
{
std::string dumpDOT() {
std::stringstream sst; std::stringstream sst;
sst << "digraph {" << std::endl; sst << "digraph {" << std::endl;
sst << "graph [ rankdir=LR ];" << std::endl; sst << "graph [ rankdir=LR ];" << std::endl;
sst << "BOS;" << std::endl; sst << "BOS;" << std::endl;
for (size_t p = 0; p < m_spans.size(); 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++) { Span &span = m_spans[p];
Node* np = span.nodeOfLength(ni); for (size_t ni = 0; ni <= span.maximumLength(); ni++)
if (np) { {
if (!p) { Node *np = span.nodeOfLength(ni);
if (np)
{
if (!p)
{
sst << "BOS -> " << np->currentKeyValue().value << ";" << std::endl; sst << "BOS -> " << np->currentKeyValue().value << ";" << std::endl;
} }
sst << np->currentKeyValue().value << ";" << std::endl; sst << np->currentKeyValue().value << ";" << std::endl;
if (p + ni < m_spans.size()) { if (p + ni < m_spans.size())
Span& dstSpan = m_spans[p + ni]; {
for (size_t q = 0; q <= dstSpan.maximumLength(); q++) { Span &dstSpan = m_spans[p + ni];
Node* dn = dstSpan.nodeOfLength(q); for (size_t q = 0; q <= dstSpan.maximumLength(); q++)
if (dn) { {
sst << np->currentKeyValue().value << " -> " Node *dn = dstSpan.nodeOfLength(q);
<< dn->currentKeyValue().value << ";" << std::endl; 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 << " -> " sst << np->currentKeyValue().value << " -> "
<< "EOS;" << std::endl; << "EOS;" << std::endl;
} }
} }
} }
} }
sst << "EOS;" << std::endl; sst << "EOS;" << std::endl;
sst << "}"; sst << "}";
return sst.str(); return sst.str();
} }
protected: protected:
std::vector<Span> m_spans; std::vector<Span> 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, inline void Grid::insertNode(const Node &node, size_t location, size_t spanningLength)
size_t spanningLength) { {
if (location >= m_spans.size()) { if (location >= m_spans.size())
{
size_t diff = location - m_spans.size() + 1; 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.push_back(Span());
} }
} }
m_spans[location].insertNodeOfLength(node, spanningLength); m_spans[location].insertNodeOfLength(node, spanningLength);
} }
inline bool Grid::hasNodeAtLocationSpanningLengthMatchingKey( inline bool Grid::hasNodeAtLocationSpanningLengthMatchingKey(size_t location, size_t spanningLength,
size_t location, size_t spanningLength, const std::string& key) { const std::string &key)
if (location > m_spans.size()) { {
if (location > m_spans.size())
{
return false; return false;
} }
const Node* n = m_spans[location].nodeOfLength(spanningLength); const Node *n = m_spans[location].nodeOfLength(spanningLength);
if (!n) { if (!n)
{
return false; return false;
} }
return key == n->key(); return key == n->key();
} }
inline void Grid::expandGridByOneAtLocation(size_t location) { inline void Grid::expandGridByOneAtLocation(size_t location)
if (!location || location == m_spans.size()) { {
if (!location || location == m_spans.size())
{
m_spans.insert(m_spans.begin() + location, Span()); m_spans.insert(m_spans.begin() + location, Span());
} else { }
else
{
m_spans.insert(m_spans.begin() + location, Span()); 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 // zaps overlapping spans
m_spans[i].removeNodeOfLengthGreaterThan(location - i); m_spans[i].removeNodeOfLengthGreaterThan(location - i);
} }
} }
} }
inline void Grid::shrinkGridByOneAtLocation(size_t location) { inline void Grid::shrinkGridByOneAtLocation(size_t location)
if (location >= m_spans.size()) { {
if (location >= m_spans.size())
{
return; return;
} }
m_spans.erase(m_spans.begin() + location); 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 // zaps overlapping spans
m_spans[i].removeNodeOfLengthGreaterThan(location - i); 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<NodeAnchor> Grid::nodesEndingAt(size_t location) { inline std::vector<NodeAnchor> Grid::nodesEndingAt(size_t location)
{
std::vector<NodeAnchor> result; std::vector<NodeAnchor> result;
if (m_spans.size() && location <= m_spans.size()) { if (m_spans.size() && location <= m_spans.size())
for (size_t i = 0; i < location; i++) { {
Span& span = m_spans[i]; for (size_t i = 0; i < location; i++)
if (i + span.maximumLength() >= location) { {
Node* np = span.nodeOfLength(location - i); Span &span = m_spans[i];
if (np) { if (i + span.maximumLength() >= location)
{
Node *np = span.nodeOfLength(location - i);
if (np)
{
NodeAnchor na; NodeAnchor na;
na.node = np; na.node = np;
na.location = i; na.location = i;
na.spanningLength = location - i; na.spanningLength = location - i;
result.push_back(na); result.push_back(na);
} }
} }
} }
} }
return result; return result;
} }
inline std::vector<NodeAnchor> Grid::nodesCrossingOrEndingAt(size_t location) { inline std::vector<NodeAnchor> Grid::nodesCrossingOrEndingAt(size_t location)
{
std::vector<NodeAnchor> result; std::vector<NodeAnchor> result;
if (m_spans.size() && location <= m_spans.size()) { if (m_spans.size() && location <= m_spans.size())
for (size_t i = 0; i < location; i++) { {
Span& span = m_spans[i]; for (size_t i = 0; i < location; i++)
{
if (i + span.maximumLength() >= location) { Span &span = m_spans[i];
for (size_t j = 1, m = span.maximumLength(); j <= m; j++) {
if (i + j < location) { if (i + span.maximumLength() >= location)
{
for (size_t j = 1, m = span.maximumLength(); j <= m; j++)
{
if (i + j < location)
{
continue; continue;
} }
Node* np = span.nodeOfLength(j); Node *np = span.nodeOfLength(j);
if (np) { if (np)
{
NodeAnchor na; NodeAnchor na;
na.node = np; na.node = np;
na.location = i; na.location = i;
na.spanningLength = location - i; na.spanningLength = location - i;
result.push_back(na); result.push_back(na);
} }
} }
} }
} }
} }
return result; return result;
} }
// For nodes found at the location, fix their currently-selected candidate using // For nodes found at the location, fix their currently-selected candidate using
// the supplied string value. // the supplied string value.
inline NodeAnchor Grid::fixNodeSelectedCandidate(size_t location, inline NodeAnchor Grid::fixNodeSelectedCandidate(size_t location, const std::string &value)
const std::string& value) { {
std::vector<NodeAnchor> nodes = nodesCrossingOrEndingAt(location); std::vector<NodeAnchor> nodes = nodesCrossingOrEndingAt(location);
NodeAnchor node; NodeAnchor node;
for (auto nodeAnchor : nodes) { for (auto nodeAnchor : nodes)
{
auto candidates = nodeAnchor.node->candidates(); auto candidates = nodeAnchor.node->candidates();
// Reset the candidate-fixed state of every node at the location. // Reset the candidate-fixed state of every node at the location.
const_cast<Node*>(nodeAnchor.node)->resetCandidate(); const_cast<Node *>(nodeAnchor.node)->resetCandidate();
for (size_t i = 0, c = candidates.size(); i < c; ++i) { for (size_t i = 0, c = candidates.size(); i < c; ++i)
if (candidates[i].value == value) { {
const_cast<Node*>(nodeAnchor.node)->selectCandidateAtIndex(i); if (candidates[i].value == value)
{
const_cast<Node *>(nodeAnchor.node)->selectCandidateAtIndex(i);
node = nodeAnchor; node = nodeAnchor;
break; break;
} }
@ -236,26 +283,28 @@ inline NodeAnchor Grid::fixNodeSelectedCandidate(size_t location,
return node; return node;
} }
inline void Grid::overrideNodeScoreForSelectedCandidate( inline void Grid::overrideNodeScoreForSelectedCandidate(size_t location, const std::string &value,
size_t location, const std::string& value, float overridingScore) { float overridingScore)
{
std::vector<NodeAnchor> nodes = nodesCrossingOrEndingAt(location); std::vector<NodeAnchor> nodes = nodesCrossingOrEndingAt(location);
for (auto nodeAnchor : nodes) { for (auto nodeAnchor : nodes)
{
auto candidates = nodeAnchor.node->candidates(); auto candidates = nodeAnchor.node->candidates();
// Reset the candidate-fixed state of every node at the location. // Reset the candidate-fixed state of every node at the location.
const_cast<Node*>(nodeAnchor.node)->resetCandidate(); const_cast<Node *>(nodeAnchor.node)->resetCandidate();
for (size_t i = 0, c = candidates.size(); i < c; ++i) { for (size_t i = 0, c = candidates.size(); i < c; ++i)
if (candidates[i].value == value) { {
const_cast<Node*>(nodeAnchor.node) if (candidates[i].value == value)
->selectFloatingCandidateAtIndex(i, overridingScore); {
const_cast<Node *>(nodeAnchor.node)->selectFloatingCandidateAtIndex(i, overridingScore);
break; break;
} }
} }
} }
} }
} // namespace Gramambular } // namespace Gramambular
#endif #endif

View File

@ -1,20 +1,27 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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_ #ifndef KEYVALUEPAIR_H_
@ -23,36 +30,42 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR TH
#include <ostream> #include <ostream>
#include <string> #include <string>
namespace Gramambular { namespace Gramambular
{
class KeyValuePair { class KeyValuePair
public: {
public:
std::string key; std::string key;
std::string value; 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, inline std::ostream &operator<<(std::ostream &stream, const KeyValuePair &pair)
const KeyValuePair& pair) { {
stream << "(" << pair.key << "," << pair.value << ")"; stream << "(" << pair.key << "," << pair.value << ")";
return stream; 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; return key == another.key && value == another.value;
} }
inline bool KeyValuePair::operator<(const KeyValuePair& another) const { inline bool KeyValuePair::operator<(const KeyValuePair &another) const
if (key < another.key) { {
if (key < another.key)
{
return true; return true;
} else if (key == another.key) { }
else if (key == another.key)
{
return value < another.value; return value < another.value;
} }
return false; return false;
} }
} // namespace Gramambular } // namespace Gramambular
#endif #endif

View File

@ -1,20 +1,27 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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_ #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 "Bigram.h"
#include "Unigram.h" #include "Unigram.h"
namespace Gramambular { namespace Gramambular
{
class LanguageModel { class LanguageModel
public: {
virtual ~LanguageModel() {} public:
virtual ~LanguageModel()
virtual const std::vector<Bigram> bigramsForKeys( {
const std::string& preceedingKey, const std::string& key) = 0; }
virtual const std::vector<Unigram> unigramsForKey(const std::string& key) = 0;
virtual bool hasUnigramsForKey(const std::string& key) = 0; virtual const std::vector<Bigram> bigramsForKeys(const std::string &preceedingKey, const std::string &key) = 0;
virtual const std::vector<Unigram> unigramsForKey(const std::string &key) = 0;
virtual bool hasUnigramsForKey(const std::string &key) = 0;
}; };
} // namespace Gramambular } // namespace Gramambular
#endif #endif

View File

@ -1,20 +1,27 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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_ #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" #include "LanguageModel.h"
namespace Gramambular { namespace Gramambular
{
class Node { class Node
public: {
public:
Node(); Node();
Node(const std::string& key, const std::vector<Unigram>& unigrams, Node(const std::string &key, const std::vector<Unigram> &unigrams, const std::vector<Bigram> &bigrams);
const std::vector<Bigram>& bigrams);
void primeNodeWithPreceedingKeyValues(const std::vector<KeyValuePair> &keyValues);
void primeNodeWithPreceedingKeyValues(
const std::vector<KeyValuePair>& keyValues);
bool isCandidateFixed() const; bool isCandidateFixed() const;
const std::vector<KeyValuePair>& candidates() const; const std::vector<KeyValuePair> &candidates() const;
void selectCandidateAtIndex(size_t index = 0, bool fix = true); void selectCandidateAtIndex(size_t index = 0, bool fix = true);
void resetCandidate(); void resetCandidate();
void selectFloatingCandidateAtIndex(size_t index, double score); void selectFloatingCandidateAtIndex(size_t index, double score);
const std::string& key() const; const std::string &key() const;
double score() const; double score() const;
double scoreForCandidate(const std::string& candidate) const; double scoreForCandidate(const std::string &candidate) const;
const KeyValuePair currentKeyValue() const; const KeyValuePair currentKeyValue() const;
double highestUnigramScore() const; double highestUnigramScore() const;
protected: protected:
const LanguageModel* m_LM; const LanguageModel *m_LM;
std::string m_key; std::string m_key;
double m_score; double m_score;
std::vector<Unigram> m_unigrams; std::vector<Unigram> m_unigrams;
std::vector<KeyValuePair> m_candidates; std::vector<KeyValuePair> m_candidates;
std::map<std::string, size_t> m_valueUnigramIndexMap; std::map<std::string, size_t> m_valueUnigramIndexMap;
std::map<KeyValuePair, std::vector<Bigram> > m_preceedingGramBigramMap; std::map<KeyValuePair, std::vector<Bigram>> m_preceedingGramBigramMap;
bool m_candidateFixed; bool m_candidateFixed;
size_t m_selectedUnigramIndex; 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) { inline std::ostream &operator<<(std::ostream &stream, const Node &node)
stream << "(node,key:" << node.m_key {
<< ",fixed:" << (node.m_candidateFixed ? "true" : "false") stream << "(node,key:" << node.m_key << ",fixed:" << (node.m_candidateFixed ? "true" : "false")
<< ",selected:" << node.m_selectedUnigramIndex << "," << ",selected:" << node.m_selectedUnigramIndex << "," << node.m_unigrams << ")";
<< node.m_unigrams << ")";
return stream; return stream;
} }
inline Node::Node() inline Node::Node() : m_candidateFixed(false), m_selectedUnigramIndex(0), m_score(0.0)
: m_candidateFixed(false), m_selectedUnigramIndex(0), m_score(0.0) {} {
}
inline Node::Node(const std::string& key, const std::vector<Unigram>& unigrams, inline Node::Node(const std::string &key, const std::vector<Unigram> &unigrams, const std::vector<Bigram> &bigrams)
const std::vector<Bigram>& bigrams) : m_key(key), m_unigrams(unigrams), m_candidateFixed(false), m_selectedUnigramIndex(0), m_score(0.0)
: 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); stable_sort(m_unigrams.begin(), m_unigrams.end(), Unigram::ScoreCompare);
if (m_unigrams.size()) { if (m_unigrams.size())
{
m_score = m_unigrams[0].score; m_score = m_unigrams[0].score;
} }
size_t i = 0; size_t i = 0;
for (std::vector<Unigram>::const_iterator ui = m_unigrams.begin(); for (std::vector<Unigram>::const_iterator ui = m_unigrams.begin(); ui != m_unigrams.end(); ++ui)
ui != m_unigrams.end(); ++ui) { {
m_valueUnigramIndexMap[(*ui).keyValue.value] = i; m_valueUnigramIndexMap[(*ui).keyValue.value] = i;
i++; i++;
m_candidates.push_back((*ui).keyValue); m_candidates.push_back((*ui).keyValue);
} }
for (std::vector<Bigram>::const_iterator bi = bigrams.begin(); for (std::vector<Bigram>::const_iterator bi = bigrams.begin(); bi != bigrams.end(); ++bi)
bi != bigrams.end(); ++bi) { {
m_preceedingGramBigramMap[(*bi).preceedingKeyValue].push_back(*bi); m_preceedingGramBigramMap[(*bi).preceedingKeyValue].push_back(*bi);
} }
} }
inline void Node::primeNodeWithPreceedingKeyValues( inline void Node::primeNodeWithPreceedingKeyValues(const std::vector<KeyValuePair> &keyValues)
const std::vector<KeyValuePair>& keyValues) { {
size_t newIndex = m_selectedUnigramIndex; size_t newIndex = m_selectedUnigramIndex;
double max = m_score; double max = m_score;
if (!isCandidateFixed()) { if (!isCandidateFixed())
for (std::vector<KeyValuePair>::const_iterator kvi = keyValues.begin(); {
kvi != keyValues.end(); ++kvi) { for (std::vector<KeyValuePair>::const_iterator kvi = keyValues.begin(); kvi != keyValues.end(); ++kvi)
std::map<KeyValuePair, std::vector<Bigram> >::const_iterator f = {
m_preceedingGramBigramMap.find(*kvi); std::map<KeyValuePair, std::vector<Bigram>>::const_iterator f = m_preceedingGramBigramMap.find(*kvi);
if (f != m_preceedingGramBigramMap.end()) { if (f != m_preceedingGramBigramMap.end())
const std::vector<Bigram>& bigrams = (*f).second; {
const std::vector<Bigram> &bigrams = (*f).second;
for (std::vector<Bigram>::const_iterator bi = bigrams.begin();
bi != bigrams.end(); ++bi) { for (std::vector<Bigram>::const_iterator bi = bigrams.begin(); bi != bigrams.end(); ++bi)
const Bigram& bigram = *bi; {
if (bigram.score > max) { const Bigram &bigram = *bi;
if (bigram.score > max)
{
std::map<std::string, size_t>::const_iterator uf = std::map<std::string, size_t>::const_iterator uf =
m_valueUnigramIndexMap.find((*bi).keyValue.value); m_valueUnigramIndexMap.find((*bi).keyValue.value);
if (uf != m_valueUnigramIndexMap.end()) { if (uf != m_valueUnigramIndexMap.end())
{
newIndex = (*uf).second; newIndex = (*uf).second;
max = bigram.score; max = bigram.score;
} }
@ -134,80 +141,109 @@ inline void Node::primeNodeWithPreceedingKeyValues(
} }
} }
} }
if (m_score != max) { if (m_score != max)
{
m_score = max; m_score = max;
} }
if (newIndex != m_selectedUnigramIndex) { if (newIndex != m_selectedUnigramIndex)
{
m_selectedUnigramIndex = newIndex; m_selectedUnigramIndex = newIndex;
} }
} }
inline bool Node::isCandidateFixed() const { return m_candidateFixed; } inline bool Node::isCandidateFixed() const
{
return m_candidateFixed;
}
inline const std::vector<KeyValuePair>& Node::candidates() const { inline const std::vector<KeyValuePair> &Node::candidates() const
{
return m_candidates; return m_candidates;
} }
inline void Node::selectCandidateAtIndex(size_t index, bool fix) { inline void Node::selectCandidateAtIndex(size_t index, bool fix)
if (index >= m_unigrams.size()) { {
if (index >= m_unigrams.size())
{
m_selectedUnigramIndex = 0; m_selectedUnigramIndex = 0;
} else { }
else
{
m_selectedUnigramIndex = index; m_selectedUnigramIndex = index;
} }
m_candidateFixed = fix; m_candidateFixed = fix;
m_score = 99; m_score = 99;
} }
inline void Node::resetCandidate() { inline void Node::resetCandidate()
{
m_selectedUnigramIndex = 0; m_selectedUnigramIndex = 0;
m_candidateFixed = 0; m_candidateFixed = 0;
if (m_unigrams.size()) { if (m_unigrams.size())
{
m_score = m_unigrams[0].score; m_score = m_unigrams[0].score;
} }
} }
inline void Node::selectFloatingCandidateAtIndex(size_t index, double score) { inline void Node::selectFloatingCandidateAtIndex(size_t index, double score)
if (index >= m_unigrams.size()) { {
if (index >= m_unigrams.size())
{
m_selectedUnigramIndex = 0; m_selectedUnigramIndex = 0;
} else { }
else
{
m_selectedUnigramIndex = index; m_selectedUnigramIndex = index;
} }
m_candidateFixed = false; m_candidateFixed = false;
m_score = score; 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
inline double Node::scoreForCandidate(const std::string& candidate) const { {
for (auto unigram : m_unigrams) { for (auto unigram : m_unigrams)
if (unigram.keyValue.value == candidate) { {
if (unigram.keyValue.value == candidate)
{
return unigram.score; return unigram.score;
} }
} }
return 0.0; return 0.0;
} }
inline double Node::highestUnigramScore() const { inline double Node::highestUnigramScore() const
if (m_unigrams.empty()) { {
if (m_unigrams.empty())
{
return 0.0; return 0.0;
} }
return m_unigrams[0].score; return m_unigrams[0].score;
} }
inline const KeyValuePair Node::currentKeyValue() const { inline const KeyValuePair Node::currentKeyValue() const
if (m_selectedUnigramIndex >= m_unigrams.size()) { {
if (m_selectedUnigramIndex >= m_unigrams.size())
{
return KeyValuePair(); return KeyValuePair();
} else { }
else
{
return m_candidates[m_selectedUnigramIndex]; return m_candidates[m_selectedUnigramIndex];
} }
} }
} // namespace Gramambular } // namespace Gramambular
#endif #endif

View File

@ -1,20 +1,27 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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_ #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" #include "Node.h"
namespace Gramambular { namespace Gramambular
{
struct NodeAnchor { struct NodeAnchor
const Node* node = nullptr; {
const Node *node = nullptr;
size_t location = 0; size_t location = 0;
size_t spanningLength = 0; size_t spanningLength = 0;
double accumulatedScore = 0.0; double accumulatedScore = 0.0;
}; };
inline std::ostream& operator<<(std::ostream& stream, inline std::ostream &operator<<(std::ostream &stream, const NodeAnchor &anchor)
const NodeAnchor& anchor) { {
stream << "{@(" << anchor.location << "," << anchor.spanningLength << "),"; stream << "{@(" << anchor.location << "," << anchor.spanningLength << "),";
if (anchor.node) { if (anchor.node)
{
stream << *(anchor.node); stream << *(anchor.node);
} else { }
else
{
stream << "null"; stream << "null";
} }
stream << "}"; stream << "}";
return stream; return stream;
} }
inline std::ostream& operator<<(std::ostream& stream, inline std::ostream &operator<<(std::ostream &stream, const std::vector<NodeAnchor> &anchor)
const std::vector<NodeAnchor>& anchor) { {
for (std::vector<NodeAnchor>::const_iterator i = anchor.begin(); for (std::vector<NodeAnchor>::const_iterator i = anchor.begin(); i != anchor.end(); ++i)
i != anchor.end(); ++i) { {
stream << *i; stream << *i;
if (i + 1 != anchor.end()) { if (i + 1 != anchor.end())
{
stream << "<-"; stream << "<-";
} }
} }
return stream; return stream;
} }
} // namespace Gramambular } // namespace Gramambular
#endif #endif

View File

@ -1,20 +1,27 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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_ #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" #include "Node.h"
namespace Gramambular { namespace Gramambular
class Span { {
public: class Span
{
public:
void clear(); void clear();
void insertNodeOfLength(const Node& node, size_t length); void insertNodeOfLength(const Node &node, size_t length);
void removeNodeOfLengthGreaterThan(size_t length); void removeNodeOfLengthGreaterThan(size_t length);
Node* nodeOfLength(size_t length); Node *nodeOfLength(size_t length);
size_t maximumLength() const; size_t maximumLength() const;
protected: protected:
std::map<size_t, Node> m_lengthNodeMap; std::map<size_t, Node> m_lengthNodeMap;
size_t m_maximumLength = 0; size_t m_maximumLength = 0;
}; };
inline void Span::clear() { inline void Span::clear()
{
m_lengthNodeMap.clear(); m_lengthNodeMap.clear();
m_maximumLength = 0; 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; m_lengthNodeMap[length] = node;
if (length > m_maximumLength) { if (length > m_maximumLength)
{
m_maximumLength = length; m_maximumLength = length;
} }
} }
inline void Span::removeNodeOfLengthGreaterThan(size_t length) { inline void Span::removeNodeOfLengthGreaterThan(size_t length)
if (length > m_maximumLength) { {
if (length > m_maximumLength)
{
return; return;
} }
size_t max = 0; size_t max = 0;
std::set<size_t> removeSet; std::set<size_t> removeSet;
for (std::map<size_t, Node>::iterator i = m_lengthNodeMap.begin(), for (std::map<size_t, Node>::iterator i = m_lengthNodeMap.begin(), e = m_lengthNodeMap.end(); i != e; ++i)
e = m_lengthNodeMap.end(); {
i != e; ++i) { if ((*i).first > length)
if ((*i).first > length) { {
removeSet.insert((*i).first); removeSet.insert((*i).first);
} else { }
if ((*i).first > max) { else
{
if ((*i).first > max)
{
max = (*i).first; max = (*i).first;
} }
} }
} }
for (std::set<size_t>::iterator i = removeSet.begin(), e = removeSet.end(); for (std::set<size_t>::iterator i = removeSet.begin(), e = removeSet.end(); i != e; ++i)
i != e; ++i) { {
m_lengthNodeMap.erase(*i); m_lengthNodeMap.erase(*i);
} }
m_maximumLength = max; m_maximumLength = max;
} }
inline Node* Span::nodeOfLength(size_t length) { inline Node *Span::nodeOfLength(size_t length)
{
std::map<size_t, Node>::iterator f = m_lengthNodeMap.find(length); std::map<size_t, Node>::iterator f = m_lengthNodeMap.find(length);
return f == m_lengthNodeMap.end() ? 0 : &(*f).second; return f == m_lengthNodeMap.end() ? 0 : &(*f).second;
} }
inline size_t Span::maximumLength() const { return m_maximumLength; } inline size_t Span::maximumLength() const
} // namespace Gramambular {
return m_maximumLength;
}
} // namespace Gramambular
#endif #endif

View File

@ -1,20 +1,27 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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_ #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" #include "KeyValuePair.h"
namespace Gramambular { namespace Gramambular
{
class Unigram { class Unigram
public: {
public:
Unigram(); Unigram();
KeyValuePair keyValue; KeyValuePair keyValue;
double score; double score;
bool operator==(const Unigram& another) const; bool operator==(const Unigram &another) const;
bool operator<(const Unigram& another) const; bool operator<(const Unigram &another) const;
static bool ScoreCompare(const Unigram& a, const Unigram& b); 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(); std::streamsize p = stream.precision();
stream.precision(6); stream.precision(6);
stream << "(" << gram.keyValue << "," << gram.score << ")"; stream << "(" << gram.keyValue << "," << gram.score << ")";
@ -47,44 +57,52 @@ inline std::ostream& operator<<(std::ostream& stream, const Unigram& gram) {
return stream; return stream;
} }
inline std::ostream& operator<<(std::ostream& stream, inline std::ostream &operator<<(std::ostream &stream, const std::vector<Unigram> &grams)
const std::vector<Unigram>& grams) { {
stream << "[" << grams.size() << "]=>{"; stream << "[" << grams.size() << "]=>{";
size_t index = 0; size_t index = 0;
for (std::vector<Unigram>::const_iterator gi = grams.begin(); for (std::vector<Unigram>::const_iterator gi = grams.begin(); gi != grams.end(); ++gi, ++index)
gi != grams.end(); ++gi, ++index) { {
stream << index << "=>"; stream << index << "=>";
stream << *gi; stream << *gi;
if (gi + 1 != grams.end()) { if (gi + 1 != grams.end())
{
stream << ","; stream << ",";
} }
} }
stream << "}"; stream << "}";
return 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; return keyValue == another.keyValue && score == another.score;
} }
inline bool Unigram::operator<(const Unigram& another) const { inline bool Unigram::operator<(const Unigram &another) const
if (keyValue < another.keyValue) { {
if (keyValue < another.keyValue)
{
return true; return true;
} else if (keyValue == another.keyValue) { }
else if (keyValue == another.keyValue)
{
return score < another.score; return score < another.score;
} }
return false; 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; return a.score > b.score;
} }
} // namespace Gramambular } // namespace Gramambular
#endif #endif

View File

@ -1,20 +1,27 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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_ #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" #include "Grid.h"
namespace Gramambular { namespace Gramambular
{
class Walker { class Walker
public: {
explicit Walker(Grid* inGrid); public:
const std::vector<NodeAnchor> reverseWalk(size_t location, explicit Walker(Grid *inGrid);
double accumulatedScore = 0.0); const std::vector<NodeAnchor> reverseWalk(size_t location, double accumulatedScore = 0.0);
protected: protected:
Grid* m_grid; Grid *m_grid;
}; };
inline Walker::Walker(Grid* inGrid) : m_grid(inGrid) {} inline Walker::Walker(Grid *inGrid) : m_grid(inGrid)
{
}
inline const std::vector<NodeAnchor> Walker::reverseWalk( inline const std::vector<NodeAnchor> Walker::reverseWalk(size_t location, double accumulatedScore)
size_t location, double accumulatedScore) { {
if (!location || location > m_grid->width()) { if (!location || location > m_grid->width())
{
return std::vector<NodeAnchor>(); return std::vector<NodeAnchor>();
} }
std::vector<std::vector<NodeAnchor> > paths; std::vector<std::vector<NodeAnchor>> paths;
std::vector<NodeAnchor> nodes = m_grid->nodesEndingAt(location); std::vector<NodeAnchor> nodes = m_grid->nodesEndingAt(location);
for (std::vector<NodeAnchor>::iterator ni = nodes.begin(); ni != nodes.end(); for (std::vector<NodeAnchor>::iterator ni = nodes.begin(); ni != nodes.end(); ++ni)
++ni) { {
if (!(*ni).node) { if (!(*ni).node)
{
continue; continue;
} }
(*ni).accumulatedScore = accumulatedScore + (*ni).node->score(); (*ni).accumulatedScore = accumulatedScore + (*ni).node->score();
std::vector<NodeAnchor> path = std::vector<NodeAnchor> path = reverseWalk(location - (*ni).spanningLength, (*ni).accumulatedScore);
reverseWalk(location - (*ni).spanningLength, (*ni).accumulatedScore);
path.insert(path.begin(), *ni); path.insert(path.begin(), *ni);
paths.push_back(path); paths.push_back(path);
} }
if (!paths.size()) { if (!paths.size())
{
return std::vector<NodeAnchor>(); return std::vector<NodeAnchor>();
} }
std::vector<NodeAnchor>* result = &*(paths.begin()); std::vector<NodeAnchor> *result = &*(paths.begin());
for (std::vector<std::vector<NodeAnchor> >::iterator pi = paths.begin(); for (std::vector<std::vector<NodeAnchor>>::iterator pi = paths.begin(); pi != paths.end(); ++pi)
pi != paths.end(); ++pi) { {
if ((*pi).back().accumulatedScore > result->back().accumulatedScore) { if ((*pi).back().accumulatedScore > result->back().accumulatedScore)
{
result = &*pi; result = &*pi;
} }
} }
return *result; return *result;
} }
} // namespace Gramambular } // namespace Gramambular
#endif #endif

View File

@ -1,59 +1,65 @@
// Copyright (c) 2022 and onwards Isaac Xen (MIT License). // 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). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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 Cocoa
@objc public class clsSFX: NSObject, NSSoundDelegate { @objc public class clsSFX: NSObject, NSSoundDelegate {
private static let shared = clsSFX() private static let shared = clsSFX()
private override init(){ private override init() {
super.init() super.init()
} }
private var currentBeep: NSSound? private var currentBeep: NSSound?
private func beep() { private func beep() {
// Stop existing beep // Stop existing beep
if let beep = currentBeep { if let beep = currentBeep {
if beep.isPlaying { if beep.isPlaying {
beep.stop() beep.stop()
} }
} }
// Create a new beep sound if possible // Create a new beep sound if possible
var sndBeep:String var sndBeep: String
if mgrPrefs.shouldNotFartInLieuOfBeep == false { if mgrPrefs.shouldNotFartInLieuOfBeep == false {
sndBeep = "Fart" sndBeep = "Fart"
} else { } else {
sndBeep = "Beep" sndBeep = "Beep"
} }
guard guard
let beep = NSSound(named:sndBeep) let beep = NSSound(named: sndBeep)
else { else {
NSSound.beep() NSSound.beep()
return return
} }
beep.delegate = self beep.delegate = self
beep.volume = 0.4 beep.volume = 0.4
beep.play() beep.play()
currentBeep = beep currentBeep = beep
} }
@objc public func sound(_ sound: NSSound, didFinishPlaying flag: Bool) { @objc public func sound(_ sound: NSSound, didFinishPlaying flag: Bool) {
currentBeep = nil currentBeep = nil
} }
@objc static func beep() { @objc static func beep() {
shared.beep() shared.beep()
} }
} }

View File

@ -1,20 +1,27 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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 Cocoa
@ -23,30 +30,32 @@ import InputMethodKit
let kConnectionName = "vChewing_1_Connection" let kConnectionName = "vChewing_1_Connection"
if CommandLine.arguments.count > 1 { if CommandLine.arguments.count > 1 {
if CommandLine.arguments[1] == "install" { if CommandLine.arguments[1] == "install" {
let exitCode = IME.registerInputMethod() let exitCode = IME.registerInputMethod()
exit(exitCode) exit(exitCode)
} }
if CommandLine.arguments[1] == "uninstall" { if CommandLine.arguments[1] == "uninstall" {
let exitCode = IME.uninstall(isSudo: IME.isSudoMode) let exitCode = IME.uninstall(isSudo: IME.isSudoMode)
exit(exitCode) exit(exitCode)
} }
} }
guard let mainNibName = Bundle.main.infoDictionary?["NSMainNibFile"] as? String else { guard let mainNibName = Bundle.main.infoDictionary?["NSMainNibFile"] as? String else {
NSLog("Fatal error: NSMainNibFile key not defined in Info.plist."); NSLog("Fatal error: NSMainNibFile key not defined in Info.plist.")
exit(-1) exit(-1)
} }
let loaded = Bundle.main.loadNibNamed(mainNibName, owner: NSApp, topLevelObjects: nil) let loaded = Bundle.main.loadNibNamed(mainNibName, owner: NSApp, topLevelObjects: nil)
if !loaded { if !loaded {
NSLog("Fatal error: Cannot load \(mainNibName).") NSLog("Fatal error: Cannot load \(mainNibName).")
exit(-1) exit(-1)
} }
guard let bundleID = Bundle.main.bundleIdentifier, let server = IMKServer(name: kConnectionName, bundleIdentifier: bundleID) else { guard let bundleID = Bundle.main.bundleIdentifier,
NSLog("Fatal error: Cannot initialize input method server with connection \(kConnectionName).") let server = IMKServer(name: kConnectionName, bundleIdentifier: bundleID)
exit(-1) else {
NSLog("Fatal error: Cannot initialize input method server with connection \(kConnectionName).")
exit(-1)
} }
NSApp.run() NSApp.run()

View File

@ -1,165 +0,0 @@
// 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).
/*
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
@objc(VTCandidateKeyLabel)
public class CandidateKeyLabel: NSObject {
@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()
}
}
@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)
}
@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 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 showNextPage() -> Bool {
false
}
@objc public func showPreviousPage() -> Bool {
false
}
@objc public func highlightNextCandidate() -> Bool {
false
}
@objc public func highlightPreviousCandidate() -> Bool {
false
}
@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)
}
}
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
}
}
if adjustedHeight > screenFrame.size.height / 2.0 {
adjustedHeight = 0.0
}
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
}
// 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
}
// left
if adjustedPoint.x < screenFrame.minX {
adjustedPoint.x = screenFrame.minX
}
window?.setFrameTopLeftPoint(adjustedPoint)
}
}

View File

@ -1,411 +0,0 @@
// 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).
/*
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
fileprivate 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
override var isFlipped: Bool {
true
}
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
}
@objc(setKeyLabels:displayedCandidates:)
func set(keyLabels labels: [String], displayedCandidates candidates: [String]) {
let count = min(labels.count, candidates.count)
keyLabels = Array(labels[0..<count])
displayedCandidates = Array(candidates[0..<count])
dispCandidatesWithLabels = zip(keyLabels,displayedCandidates).map() {$0 + $1}
var newWidths = [CGFloat]()
let baseSize = NSSize(width: 10240.0, height: 10240.0)
for index in 0..<count {
let rctCandidate = (dispCandidatesWithLabels[index] as NSString).boundingRect(with: baseSize, options: .usesLineFragmentOrigin, attributes: candidateWithLabelAttrDict)
var cellWidth = rctCandidate.size.width + cellPadding
let cellHeight = rctCandidate.size.height + cellPadding
if cellWidth < cellHeight * 1.35 {
cellWidth = cellHeight * 1.35
}
newWidths.append(cellWidth)
}
elementWidths = newWidths
}
@objc(setKeyLabelFont:candidateFont:)
func set(keyLabelFont labelFont: NSFont, candidateFont: NSFont) {
let paraStyle = NSMutableParagraphStyle()
paraStyle.setParagraphStyle(NSParagraphStyle.default)
paraStyle.alignment = .center
candidateWithLabelAttrDict = [.font: candidateFont,
.paragraphStyle: paraStyle,
.foregroundColor: NSColor.labelColor] // We still need this dummy section to make sure the space occupations of the candidates are correct.
keyLabelAttrDict = [.font: labelFont,
.paragraphStyle: paraStyle,
.verticalGlyphForm: true as AnyObject,
.foregroundColor: NSColor.secondaryLabelColor] // Candidate phrase text color
candidateAttrDict = [.font: candidateFont,
.paragraphStyle: paraStyle,
.foregroundColor: NSColor.labelColor] // Candidate index text color
let labelFontSize = labelFont.pointSize
let candidateFontSize = candidateFont.pointSize
let biggestSize = max(labelFontSize, candidateFontSize)
keyLabelWidth = ceil(labelFontSize)
keyLabelHeight = ceil(labelFontSize * 2)
candidateTextHeight = ceil(candidateFontSize * 1.20)
cellPadding = ceil(biggestSize / 2.0)
}
override func draw(_ dirtyRect: NSRect) {
let bounds = self.bounds
NSColor.controlBackgroundColor.setFill() // Candidate list panel base background
NSBezierPath.fill(bounds)
NSColor.systemGray.withAlphaComponent(0.75).setStroke()
NSBezierPath.strokeLine(from: NSPoint(x: bounds.size.width, y: 0.0), to: NSPoint(x: bounds.size.width, y: bounds.size.height))
var accuWidth: CGFloat = 0
for index in 0..<elementWidths.count {
let currentWidth = elementWidths[index]
let rctCandidateArea = NSRect(x: accuWidth, y: 0.0, width: currentWidth + 1.0, height: candidateTextHeight + cellPadding)
let rctLabel = NSRect(x: accuWidth + cellPadding / 2 - 1, y: cellPadding / 2 , width: keyLabelWidth, height: keyLabelHeight * 2.0)
let rctCandidatePhrase = NSRect(x: accuWidth + keyLabelWidth - 1, y: cellPadding / 2 , width: currentWidth - keyLabelWidth, height: candidateTextHeight)
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()
}
activeCandidateIndexAttr[.foregroundColor] = NSColor.selectedMenuItemTextColor.withAlphaComponent(0.84) // The index text color of the highlightened candidate
activeCandidateAttr[.foregroundColor] = NSColor.selectedMenuItemTextColor // The phrase text color of the highlightened candidate
} 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..<elementWidths.count {
let currentWidth = elementWidths[index]
if location.x >= accuWidth && location.x <= accuWidth + currentWidth {
return UInt(index)
}
accuWidth += currentWidth + 1.0
}
return nil
}
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
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)
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
}
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)]
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)
super.init(window: panel)
candidateView.target = self
candidateView.action = #selector(candidateViewMouseDidClick(_:))
nextPageButton.target = self
nextPageButton.action = #selector(pageButtonAction(_:))
prevPageButton.target = self
prevPageButton.action = #selector(pageButtonAction(_:))
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public override func reloadData() {
candidateView.highlightedIndex = 0
currentPage = 0
layoutCandidateView()
}
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 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 highlightNextCandidate() -> Bool {
guard let delegate = delegate else {return false}
selectedCandidateIndex = (selectedCandidateIndex + 1 >= delegate.candidateCountForController(self)) ? 0 : selectedCandidateIndex + 1
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 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 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)
let begin = currentPage * keyLabelCount
for index in begin..<min(begin + keyLabelCount, count) {
let candidate = delegate.candidateController(self, candidateAtIndex: index)
candidates.append(candidate)
}
candidateView.set(keyLabels: keyLabels.map { $0.displayedText}, displayedCandidates: candidates)
var newSize = candidateView.sizeForView
var frameRect = candidateView.frame
frameRect.size = newSize
candidateView.frame = frameRect
if pageCount > 1 && mgrPrefs.showPageButtonsInCandidateWindow {
var buttonRect = nextPageButton.frame
let spacing:CGFloat = 0.0
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
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
}
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)
}
@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)
}
}

View File

@ -1,417 +0,0 @@
// 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).
/*
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
fileprivate 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
override var isFlipped: Bool {
true
}
var sizeForView: NSSize {
var result = NSSize.zero
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..<count])
displayedCandidates = Array(candidates[0..<count])
dispCandidatesWithLabels = zip(keyLabels,displayedCandidates).map() {$0 + $1}
var newWidths = [CGFloat]()
var calculatedWindowWidth = CGFloat()
var newHeights = [CGFloat]()
let baseSize = NSSize(width: 10240.0, height: 10240.0)
for index in 0..<count {
let rctCandidate = (dispCandidatesWithLabels[index] as NSString).boundingRect(with: baseSize, options: .usesLineFragmentOrigin, attributes: candidateWithLabelAttrDict)
let cellWidth = rctCandidate.size.width + cellPadding
let cellHeight = rctCandidate.size.height + cellPadding
if (calculatedWindowWidth < rctCandidate.size.width) {
calculatedWindowWidth = rctCandidate.size.width + cellPadding
}
newWidths.append(cellWidth)
newHeights.append(cellHeight)
}
elementWidths = newWidths
elementHeights = newHeights
windowWidth = calculatedWindowWidth + cellPadding;
}
@objc(setKeyLabelFont:candidateFont:)
func set(keyLabelFont labelFont: NSFont, candidateFont: NSFont) {
let paraStyle = NSMutableParagraphStyle()
paraStyle.setParagraphStyle(NSParagraphStyle.default)
paraStyle.alignment = .left
candidateWithLabelAttrDict = [.font: candidateFont,
.paragraphStyle: paraStyle,
.foregroundColor: NSColor.labelColor] // We still need this dummy section to make sure the space occupations of the candidates are correct.
keyLabelAttrDict = [.font: labelFont,
.paragraphStyle: paraStyle,
.verticalGlyphForm: true as AnyObject,
.foregroundColor: NSColor.secondaryLabelColor] // Candidate phrase text color
candidateAttrDict = [.font: candidateFont,
.paragraphStyle: paraStyle,
.foregroundColor: NSColor.labelColor] // Candidate index text color
let labelFontSize = labelFont.pointSize
let candidateFontSize = candidateFont.pointSize
let biggestSize = max(labelFontSize, candidateFontSize)
keyLabelWidth = ceil(labelFontSize)
keyLabelHeight = ceil(labelFontSize * 2)
candidateTextHeight = ceil(candidateFontSize * 1.20)
cellPadding = ceil(biggestSize / 2.0)
}
override func draw(_ dirtyRect: NSRect) {
let bounds = self.bounds
NSColor.controlBackgroundColor.setFill() // Candidate list panel base background
NSBezierPath.fill(bounds)
NSColor.systemGray.withAlphaComponent(0.75).setStroke()
NSBezierPath.strokeLine(from: NSPoint(x: bounds.size.width, y: 0.0), to: NSPoint(x: bounds.size.width, y: bounds.size.height))
var accuHeight: CGFloat = 0
for index in 0..<elementHeights.count {
let currentHeight = elementHeights[index]
let rctCandidateArea = NSRect(x: 0.0, y: accuHeight, width: windowWidth, height: candidateTextHeight + cellPadding)
let rctLabel = NSRect(x: cellPadding / 2 - 1, y: accuHeight + cellPadding / 2, width: keyLabelWidth, height: keyLabelHeight * 2.0)
let rctCandidatePhrase = NSRect(x: cellPadding / 2 - 1 + keyLabelWidth, y: accuHeight + cellPadding / 2 - 1, width: windowWidth - keyLabelWidth, height: candidateTextHeight)
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()
}
activeCandidateIndexAttr[.foregroundColor] = NSColor.selectedMenuItemTextColor.withAlphaComponent(0.84) // The index text color of the highlightened candidate
activeCandidateAttr[.foregroundColor] = NSColor.selectedMenuItemTextColor // The phrase text color of the highlightened candidate
} 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..<elementHeights.count {
let currentHeight = elementHeights[index]
if location.y >= accuHeight && location.y <= accuHeight + currentHeight {
return UInt(index)
}
accuHeight += currentHeight
}
return nil
}
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
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)
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
}
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)]
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)
super.init(window: panel)
candidateView.target = self
candidateView.action = #selector(candidateViewMouseDidClick(_:))
nextPageButton.target = self
nextPageButton.action = #selector(pageButtonAction(_:))
prevPageButton.target = self
prevPageButton.action = #selector(pageButtonAction(_:))
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public override func reloadData() {
candidateView.highlightedIndex = 0
currentPage = 0
layoutCandidateView()
}
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 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 highlightNextCandidate() -> Bool {
guard let delegate = delegate else {return false}
selectedCandidateIndex = (selectedCandidateIndex + 1 >= delegate.candidateCountForController(self)) ? 0 : selectedCandidateIndex + 1
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 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 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 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)
let begin = currentPage * keyLabelCount
for index in begin..<min(begin + keyLabelCount, count) {
let candidate = delegate.candidateController(self, candidateAtIndex: index)
candidates.append(candidate)
}
candidateView.set(keyLabels: keyLabels.map { $0.displayedText}, displayedCandidates: candidates)
var newSize = candidateView.sizeForView
var frameRect = candidateView.frame
frameRect.size = newSize
candidateView.frame = frameRect
if pageCount > 1 && mgrPrefs.showPageButtonsInCandidateWindow {
var buttonRect = nextPageButton.frame
let spacing:CGFloat = 0.0
// 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
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
}
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)
}
@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)
}
}

View File

@ -0,0 +1,176 @@
// 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
@objc(VTCandidateKeyLabel)
public class CandidateKeyLabel: NSObject {
@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()
}
}
@objc(ctlCandidateDelegate)
public protocol ctlCandidateDelegate: AnyObject {
func candidateCountForController(_ controller: ctlCandidate) -> UInt
func ctlCandidate(_ controller: ctlCandidate, candidateAtIndex index: UInt)
-> String
func ctlCandidate(
_ controller: ctlCandidate, didSelectCandidateAtIndex index: UInt)
}
@objc(ctlCandidate)
public class ctlCandidate: NSWindowController {
@objc public weak var delegate: ctlCandidateDelegate? {
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 func reloadData() {
}
@objc public func showNextPage() -> Bool {
false
}
@objc public func showPreviousPage() -> Bool {
false
}
@objc public func highlightNextCandidate() -> Bool {
false
}
@objc public func highlightPreviousCandidate() -> Bool {
false
}
@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)
}
}
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
}
}
if adjustedHeight > screenFrame.size.height / 2.0 {
adjustedHeight = 0.0
}
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
}
// 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
}
// left
if adjustedPoint.x < screenFrame.minX {
adjustedPoint.x = screenFrame.minX
}
window?.setFrameTopLeftPoint(adjustedPoint)
}
}

View File

@ -0,0 +1,461 @@
// 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
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
override var isFlipped: Bool {
true
}
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
}
@objc(setKeyLabels:displayedCandidates:)
func set(keyLabels labels: [String], displayedCandidates candidates: [String]) {
let count = min(labels.count, candidates.count)
keyLabels = Array(labels[0..<count])
displayedCandidates = Array(candidates[0..<count])
dispCandidatesWithLabels = zip(keyLabels, displayedCandidates).map { $0 + $1 }
var newWidths = [CGFloat]()
let baseSize = NSSize(width: 10240.0, height: 10240.0)
for index in 0..<count {
let rctCandidate = (dispCandidatesWithLabels[index] as NSString).boundingRect(
with: baseSize, options: .usesLineFragmentOrigin,
attributes: candidateWithLabelAttrDict)
var cellWidth = rctCandidate.size.width + cellPadding
let cellHeight = rctCandidate.size.height + cellPadding
if cellWidth < cellHeight * 1.35 {
cellWidth = cellHeight * 1.35
}
newWidths.append(cellWidth)
}
elementWidths = newWidths
}
@objc(setKeyLabelFont:candidateFont:)
func set(keyLabelFont labelFont: NSFont, candidateFont: NSFont) {
let paraStyle = NSMutableParagraphStyle()
paraStyle.setParagraphStyle(NSParagraphStyle.default)
paraStyle.alignment = .center
candidateWithLabelAttrDict = [
.font: candidateFont,
.paragraphStyle: paraStyle,
.foregroundColor: NSColor.labelColor,
] // We still need this dummy section to make sure that
// the space occupations of the candidates are correct.
keyLabelAttrDict = [
.font: labelFont,
.paragraphStyle: paraStyle,
.verticalGlyphForm: true as AnyObject,
.foregroundColor: NSColor.secondaryLabelColor,
] // Candidate phrase text color
candidateAttrDict = [
.font: candidateFont,
.paragraphStyle: paraStyle,
.foregroundColor: NSColor.labelColor,
] // Candidate index text color
let labelFontSize = labelFont.pointSize
let candidateFontSize = candidateFont.pointSize
let biggestSize = max(labelFontSize, candidateFontSize)
keyLabelWidth = ceil(labelFontSize)
keyLabelHeight = ceil(labelFontSize * 2)
candidateTextHeight = ceil(candidateFontSize * 1.20)
cellPadding = ceil(biggestSize / 2.0)
}
override func draw(_ dirtyRect: NSRect) {
let bounds = self.bounds
NSColor.controlBackgroundColor.setFill() // Candidate list panel base background
NSBezierPath.fill(bounds)
NSColor.systemGray.withAlphaComponent(0.75).setStroke()
NSBezierPath.strokeLine(
from: NSPoint(x: bounds.size.width, y: 0.0),
to: NSPoint(x: bounds.size.width, y: bounds.size.height))
var accuWidth: CGFloat = 0
for index in 0..<elementWidths.count {
let currentWidth = elementWidths[index]
let rctCandidateArea = NSRect(
x: accuWidth, y: 0.0, width: currentWidth + 1.0,
height: candidateTextHeight + cellPadding)
let rctLabel = NSRect(
x: accuWidth + cellPadding / 2 - 1, y: cellPadding / 2, width: keyLabelWidth,
height: keyLabelHeight * 2.0)
let rctCandidatePhrase = NSRect(
x: accuWidth + keyLabelWidth - 1, y: cellPadding / 2,
width: currentWidth - keyLabelWidth,
height: candidateTextHeight)
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..<elementWidths.count {
let currentWidth = elementWidths[index]
if location.x >= accuWidth && location.x <= accuWidth + currentWidth {
return UInt(index)
}
accuWidth += currentWidth + 1.0
}
return nil
}
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 public class ctlCandidateHorizontal: ctlCandidate {
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)
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
}
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)]
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)
super.init(window: panel)
candidateView.target = self
candidateView.action = #selector(candidateViewMouseDidClick(_:))
nextPageButton.target = self
nextPageButton.action = #selector(pageButtonAction(_:))
prevPageButton.target = self
prevPageButton.action = #selector(pageButtonAction(_:))
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public override func reloadData() {
candidateView.highlightedIndex = 0
currentPage = 0
layoutCandidateView()
}
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 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 highlightNextCandidate() -> Bool {
guard let delegate = delegate else { return false }
selectedCandidateIndex =
(selectedCandidateIndex + 1 >= delegate.candidateCountForController(self))
? 0 : selectedCandidateIndex + 1
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 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 ctlCandidateHorizontal {
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
}
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..<min(begin + keyLabelCount, count) {
let candidate = delegate.ctlCandidate(self, candidateAtIndex: index)
candidates.append(candidate)
}
candidateView.set(
keyLabels: keyLabels.map { $0.displayedText }, displayedCandidates: candidates)
var newSize = candidateView.sizeForView
var frameRect = candidateView.frame
frameRect.size = newSize
candidateView.frame = frameRect
if pageCount > 1 && mgrPrefs.showPageButtonsInCandidateWindow {
var buttonRect = nextPageButton.frame
let spacing: CGFloat = 0.0
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
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
}
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)
}
@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?.ctlCandidate(self, didSelectCandidateAtIndex: selectedCandidateIndex)
}
}

View File

@ -0,0 +1,466 @@
// 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
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
override var isFlipped: Bool {
true
}
var sizeForView: NSSize {
var result = NSSize.zero
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..<count])
displayedCandidates = Array(candidates[0..<count])
dispCandidatesWithLabels = zip(keyLabels, displayedCandidates).map { $0 + $1 }
var newWidths = [CGFloat]()
var calculatedWindowWidth = CGFloat()
var newHeights = [CGFloat]()
let baseSize = NSSize(width: 10240.0, height: 10240.0)
for index in 0..<count {
let rctCandidate = (dispCandidatesWithLabels[index] as NSString).boundingRect(
with: baseSize, options: .usesLineFragmentOrigin,
attributes: candidateWithLabelAttrDict)
let cellWidth = rctCandidate.size.width + cellPadding
let cellHeight = rctCandidate.size.height + cellPadding
if calculatedWindowWidth < rctCandidate.size.width {
calculatedWindowWidth = rctCandidate.size.width + cellPadding
}
newWidths.append(cellWidth)
newHeights.append(cellHeight)
}
elementWidths = newWidths
elementHeights = newHeights
windowWidth = calculatedWindowWidth + cellPadding
}
@objc(setKeyLabelFont:candidateFont:)
func set(keyLabelFont labelFont: NSFont, candidateFont: NSFont) {
let paraStyle = NSMutableParagraphStyle()
paraStyle.setParagraphStyle(NSParagraphStyle.default)
paraStyle.alignment = .left
candidateWithLabelAttrDict = [
.font: candidateFont,
.paragraphStyle: paraStyle,
.foregroundColor: NSColor.labelColor,
] // We still need this dummy section to make sure that
// the space occupations of the candidates are correct.
keyLabelAttrDict = [
.font: labelFont,
.paragraphStyle: paraStyle,
.verticalGlyphForm: true as AnyObject,
.foregroundColor: NSColor.secondaryLabelColor,
] // Candidate phrase text color
candidateAttrDict = [
.font: candidateFont,
.paragraphStyle: paraStyle,
.foregroundColor: NSColor.labelColor,
] // Candidate index text color
let labelFontSize = labelFont.pointSize
let candidateFontSize = candidateFont.pointSize
let biggestSize = max(labelFontSize, candidateFontSize)
keyLabelWidth = ceil(labelFontSize)
keyLabelHeight = ceil(labelFontSize * 2)
candidateTextHeight = ceil(candidateFontSize * 1.20)
cellPadding = ceil(biggestSize / 2.0)
}
override func draw(_ dirtyRect: NSRect) {
let bounds = self.bounds
NSColor.controlBackgroundColor.setFill() // Candidate list panel base background
NSBezierPath.fill(bounds)
NSColor.systemGray.withAlphaComponent(0.75).setStroke()
NSBezierPath.strokeLine(
from: NSPoint(x: bounds.size.width, y: 0.0),
to: NSPoint(x: bounds.size.width, y: bounds.size.height))
var accuHeight: CGFloat = 0
for index in 0..<elementHeights.count {
let currentHeight = elementHeights[index]
let rctCandidateArea = NSRect(
x: 0.0, y: accuHeight, width: windowWidth, height: candidateTextHeight + cellPadding
)
let rctLabel = NSRect(
x: cellPadding / 2 - 1, y: accuHeight + cellPadding / 2, width: keyLabelWidth,
height: keyLabelHeight * 2.0)
let rctCandidatePhrase = NSRect(
x: cellPadding / 2 - 1 + keyLabelWidth, y: accuHeight + cellPadding / 2 - 1,
width: windowWidth - keyLabelWidth, height: candidateTextHeight)
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..<elementHeights.count {
let currentHeight = elementHeights[index]
if location.y >= accuHeight && location.y <= accuHeight + currentHeight {
return UInt(index)
}
accuHeight += currentHeight
}
return nil
}
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 public class ctlCandidateVertical: ctlCandidate {
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)
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
}
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)]
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)
super.init(window: panel)
candidateView.target = self
candidateView.action = #selector(candidateViewMouseDidClick(_:))
nextPageButton.target = self
nextPageButton.action = #selector(pageButtonAction(_:))
prevPageButton.target = self
prevPageButton.action = #selector(pageButtonAction(_:))
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public override func reloadData() {
candidateView.highlightedIndex = 0
currentPage = 0
layoutCandidateView()
}
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 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 highlightNextCandidate() -> Bool {
guard let delegate = delegate else { return false }
selectedCandidateIndex =
(selectedCandidateIndex + 1 >= delegate.candidateCountForController(self))
? 0 : selectedCandidateIndex + 1
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 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 ctlCandidateVertical {
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
}
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..<min(begin + keyLabelCount, count) {
let candidate = delegate.ctlCandidate(self, candidateAtIndex: index)
candidates.append(candidate)
}
candidateView.set(
keyLabels: keyLabels.map { $0.displayedText }, displayedCandidates: candidates)
var newSize = candidateView.sizeForView
var frameRect = candidateView.frame
frameRect.size = newSize
candidateView.frame = frameRect
if pageCount > 1 && mgrPrefs.showPageButtonsInCandidateWindow {
var buttonRect = nextPageButton.frame
let spacing: CGFloat = 0.0
// 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
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
}
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)
}
@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?.ctlCandidate(self, didSelectCandidateAtIndex: selectedCandidateIndex)
}
}

View File

@ -1,203 +1,216 @@
// Copyright (c) 2021 and onwards Weizhong Yang (MIT License). // 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). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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 Cocoa
private protocol NotifierWindowDelegate: AnyObject { private protocol NotifierWindowDelegate: AnyObject {
func windowDidBecomeClicked(_ window: NotifierWindow) func windowDidBecomeClicked(_ window: NotifierWindow)
} }
private class NotifierWindow: NSWindow { private class NotifierWindow: NSWindow {
weak var clickDelegate: NotifierWindowDelegate? weak var clickDelegate: NotifierWindowDelegate?
override func mouseDown(with event: NSEvent) { override func mouseDown(with event: NSEvent) {
clickDelegate?.windowDidBecomeClicked(self) clickDelegate?.windowDidBecomeClicked(self)
} }
} }
private let kWindowWidth: CGFloat = 213.0 private let kWindowWidth: CGFloat = 213.0
private let kWindowHeight: CGFloat = 60.0 private let kWindowHeight: CGFloat = 60.0
public class NotifierController: NSWindowController, NotifierWindowDelegate { public class NotifierController: NSWindowController, NotifierWindowDelegate {
private var messageTextField: NSTextField private var messageTextField: NSTextField
private var message: String = "" { private var message: String = "" {
didSet { didSet {
let paraStyle = NSMutableParagraphStyle() let paraStyle = NSMutableParagraphStyle()
paraStyle.setParagraphStyle(NSParagraphStyle.default) paraStyle.setParagraphStyle(NSParagraphStyle.default)
paraStyle.alignment = .center paraStyle.alignment = .center
let attr: [NSAttributedString.Key: AnyObject] = [ let attr: [NSAttributedString.Key: AnyObject] = [
.foregroundColor: foregroundColor, .foregroundColor: foregroundColor,
.font: NSFont.boldSystemFont(ofSize: NSFont.systemFontSize(for: .regular)), .font: NSFont.boldSystemFont(ofSize: NSFont.systemFontSize(for: .regular)),
.paragraphStyle: paraStyle .paragraphStyle: paraStyle,
] ]
let attrString = NSAttributedString(string: message, attributes: attr) let attrString = NSAttributedString(string: message, attributes: attr)
messageTextField.attributedStringValue = attrString messageTextField.attributedStringValue = attrString
let width = window?.frame.width ?? kWindowWidth let width = window?.frame.width ?? kWindowWidth
let rect = attrString.boundingRect(with: NSSize(width: width, height: 1600), options: .usesLineFragmentOrigin) let rect = attrString.boundingRect(
let height = rect.height with: NSSize(width: width, height: 1600), options: .usesLineFragmentOrigin)
let x = messageTextField.frame.origin.x let height = rect.height
let y = ((window?.frame.height ?? kWindowHeight) - height) / 2 let x = messageTextField.frame.origin.x
let newFrame = NSRect(x: x, y: y, width: width, height: height) let y = ((window?.frame.height ?? kWindowHeight) - height) / 2
messageTextField.frame = newFrame let newFrame = NSRect(x: x, y: y, width: width, height: height)
} messageTextField.frame = newFrame
} }
private var shouldStay: Bool = false }
private var backgroundColor: NSColor = .textBackgroundColor { private var shouldStay: Bool = false
didSet { private var backgroundColor: NSColor = .textBackgroundColor {
self.window?.backgroundColor = backgroundColor didSet {
} self.window?.backgroundColor = backgroundColor
} }
private var foregroundColor: NSColor = .controlTextColor { }
didSet { private var foregroundColor: NSColor = .controlTextColor {
self.messageTextField.textColor = foregroundColor didSet {
} self.messageTextField.textColor = foregroundColor
} }
private var waitTimer: Timer? }
private var fadeTimer: Timer? private var waitTimer: Timer?
private var fadeTimer: Timer?
private static var instanceCount = 0 private static var instanceCount = 0
private static var lastLocation = NSPoint.zero private static var lastLocation = NSPoint.zero
@objc public static func notify(message: String, stay: Bool = false) { @objc public static func notify(message: String, stay: Bool = false) {
let controller = NotifierController() let controller = NotifierController()
controller.message = message controller.message = message
controller.shouldStay = stay controller.shouldStay = stay
controller.show() controller.show()
} }
private static func increaseInstanceCount() { private static func increaseInstanceCount() {
instanceCount += 1 instanceCount += 1
} }
private static func decreaseInstanceCount() { private static func decreaseInstanceCount() {
instanceCount -= 1 instanceCount -= 1
if instanceCount < 0 { if instanceCount < 0 {
instanceCount = 0 instanceCount = 0
} }
} }
private init() { private init() {
let screenRect = NSScreen.main?.visibleFrame ?? NSRect.zero let screenRect = NSScreen.main?.visibleFrame ?? NSRect.zero
let contentRect = NSRect(x: 0, y: 0, width: kWindowWidth, height: kWindowHeight) let contentRect = NSRect(x: 0, y: 0, width: kWindowWidth, height: kWindowHeight)
var windowRect = contentRect var windowRect = contentRect
windowRect.origin.x = screenRect.maxX - windowRect.width - 10 windowRect.origin.x = screenRect.maxX - windowRect.width - 10
windowRect.origin.y = screenRect.maxY - windowRect.height - 10 windowRect.origin.y = screenRect.maxY - windowRect.height - 10
let styleMask: NSWindow.StyleMask = [.fullSizeContentView, .titled] let styleMask: NSWindow.StyleMask = [.fullSizeContentView, .titled]
let transparentVisualEffect = NSVisualEffectView() let transparentVisualEffect = NSVisualEffectView()
transparentVisualEffect.blendingMode = .behindWindow transparentVisualEffect.blendingMode = .behindWindow
transparentVisualEffect.state = .active transparentVisualEffect.state = .active
let panel = NotifierWindow(contentRect: windowRect, styleMask: styleMask, backing: .buffered, defer: false) let panel = NotifierWindow(
panel.contentView = transparentVisualEffect contentRect: windowRect, styleMask: styleMask, backing: .buffered, defer: false)
panel.isMovableByWindowBackground = true panel.contentView = transparentVisualEffect
panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel)) panel.isMovableByWindowBackground = true
panel.hasShadow = true panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel))
panel.backgroundColor = backgroundColor panel.hasShadow = true
panel.title = "" panel.backgroundColor = backgroundColor
panel.titlebarAppearsTransparent = true panel.title = ""
panel.titleVisibility = .hidden panel.titlebarAppearsTransparent = true
panel.showsToolbarButton = false panel.titleVisibility = .hidden
panel.standardWindowButton(NSWindow.ButtonType.fullScreenButton)?.isHidden = true panel.showsToolbarButton = false
panel.standardWindowButton(NSWindow.ButtonType.miniaturizeButton)?.isHidden = true panel.standardWindowButton(NSWindow.ButtonType.fullScreenButton)?.isHidden = true
panel.standardWindowButton(NSWindow.ButtonType.closeButton)?.isHidden = true panel.standardWindowButton(NSWindow.ButtonType.miniaturizeButton)?.isHidden = true
panel.standardWindowButton(NSWindow.ButtonType.zoomButton)?.isHidden = true panel.standardWindowButton(NSWindow.ButtonType.closeButton)?.isHidden = true
panel.standardWindowButton(NSWindow.ButtonType.zoomButton)?.isHidden = true
messageTextField = NSTextField() messageTextField = NSTextField()
messageTextField.frame = contentRect messageTextField.frame = contentRect
messageTextField.isEditable = false messageTextField.isEditable = false
messageTextField.isSelectable = false messageTextField.isSelectable = false
messageTextField.isBezeled = false messageTextField.isBezeled = false
messageTextField.textColor = foregroundColor messageTextField.textColor = foregroundColor
messageTextField.drawsBackground = false messageTextField.drawsBackground = false
messageTextField.font = .boldSystemFont(ofSize: NSFont.systemFontSize(for: .regular)) messageTextField.font = .boldSystemFont(ofSize: NSFont.systemFontSize(for: .regular))
panel.contentView?.addSubview(messageTextField) panel.contentView?.addSubview(messageTextField)
super.init(window: panel) super.init(window: panel)
panel.clickDelegate = self panel.clickDelegate = self
} }
required init?(coder: NSCoder) { required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
private func show() { private func show() {
func setStartLocation() { func setStartLocation() {
if NotifierController.instanceCount == 0 { if NotifierController.instanceCount == 0 {
return return
} }
let lastLocation = NotifierController.lastLocation let lastLocation = NotifierController.lastLocation
let screenRect = NSScreen.main?.visibleFrame ?? NSRect.zero let screenRect = NSScreen.main?.visibleFrame ?? NSRect.zero
var windowRect = self.window?.frame ?? NSRect.zero var windowRect = self.window?.frame ?? NSRect.zero
windowRect.origin.x = lastLocation.x windowRect.origin.x = lastLocation.x
windowRect.origin.y = lastLocation.y - 10 - windowRect.height windowRect.origin.y = lastLocation.y - 10 - windowRect.height
if windowRect.origin.y < screenRect.minY { if windowRect.origin.y < screenRect.minY {
return return
} }
self.window?.setFrame(windowRect, display: true) self.window?.setFrame(windowRect, display: true)
} }
func moveIn() { func moveIn() {
let afterRect = self.window?.frame ?? NSRect.zero let afterRect = self.window?.frame ?? NSRect.zero
NotifierController.lastLocation = afterRect.origin NotifierController.lastLocation = afterRect.origin
var beforeRect = afterRect var beforeRect = afterRect
beforeRect.origin.y += 10 beforeRect.origin.y += 10
window?.setFrame(beforeRect, display: true) window?.setFrame(beforeRect, display: true)
window?.orderFront(self) window?.orderFront(self)
window?.setFrame(afterRect, display: true, animate: true) window?.setFrame(afterRect, display: true, animate: true)
} }
setStartLocation() setStartLocation()
moveIn() moveIn()
NotifierController.increaseInstanceCount() NotifierController.increaseInstanceCount()
waitTimer = Timer.scheduledTimer(timeInterval: shouldStay ? 5 : 1, target: self, selector: #selector(fadeOut), userInfo: nil, repeats: false) waitTimer = Timer.scheduledTimer(
} timeInterval: shouldStay ? 5 : 1, target: self, selector: #selector(fadeOut),
userInfo: nil,
repeats: false)
}
@objc private func doFadeOut(_ timer: Timer) { @objc private func doFadeOut(_ timer: Timer) {
let opacity = self.window?.alphaValue ?? 0 let opacity = self.window?.alphaValue ?? 0
if opacity <= 0 { if opacity <= 0 {
self.close() self.close()
} else { } else {
self.window?.alphaValue = opacity - 0.2 self.window?.alphaValue = opacity - 0.2
} }
} }
@objc private func fadeOut() { @objc private func fadeOut() {
waitTimer?.invalidate() waitTimer?.invalidate()
waitTimer = nil waitTimer = nil
NotifierController.decreaseInstanceCount() NotifierController.decreaseInstanceCount()
fadeTimer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(doFadeOut(_:)), userInfo: nil, repeats: true) fadeTimer = Timer.scheduledTimer(
} timeInterval: 0.01, target: self, selector: #selector(doFadeOut(_:)), userInfo: nil,
repeats: true)
}
public override func close() { public override func close() {
waitTimer?.invalidate() waitTimer?.invalidate()
waitTimer = nil waitTimer = nil
fadeTimer?.invalidate() fadeTimer?.invalidate()
fadeTimer = nil fadeTimer = nil
super.close() super.close()
} }
fileprivate func windowDidBecomeClicked(_ window: NotifierWindow) { fileprivate func windowDidBecomeClicked(_ window: NotifierWindow) {
self.fadeOut() self.fadeOut()
} }
} }

View File

@ -1,122 +1,129 @@
// Copyright (c) 2021 and onwards Weizhong Yang (MIT License). // 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). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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 Cocoa
public class TooltipController: NSWindowController { public class TooltipController: NSWindowController {
static var backgroundColor = NSColor.windowBackgroundColor static var backgroundColor = NSColor.windowBackgroundColor
static var textColor = NSColor.windowBackgroundColor static var textColor = NSColor.windowBackgroundColor
private var messageTextField: NSTextField private var messageTextField: NSTextField
private var tooltip: String = "" { private var tooltip: String = "" {
didSet { didSet {
messageTextField.stringValue = tooltip messageTextField.stringValue = tooltip
adjustSize() adjustSize()
} }
} }
public init() { public init() {
let contentRect = NSRect(x: 128.0, y: 128.0, width: 300.0, height: 20.0) let contentRect = NSRect(x: 128.0, y: 128.0, width: 300.0, height: 20.0)
let styleMask: NSWindow.StyleMask = [.borderless, .nonactivatingPanel] let styleMask: NSWindow.StyleMask = [.borderless, .nonactivatingPanel]
let panel = NSPanel(contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false) let panel = NSPanel(
panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel) + 1) contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false)
panel.hasShadow = true panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel) + 1)
panel.hasShadow = true
messageTextField = NSTextField() messageTextField = NSTextField()
messageTextField.isEditable = false messageTextField.isEditable = false
messageTextField.isSelectable = false messageTextField.isSelectable = false
messageTextField.isBezeled = false messageTextField.isBezeled = false
messageTextField.textColor = TooltipController.textColor messageTextField.textColor = TooltipController.textColor
messageTextField.drawsBackground = true messageTextField.drawsBackground = true
messageTextField.backgroundColor = TooltipController.backgroundColor messageTextField.backgroundColor = TooltipController.backgroundColor
messageTextField.font = .systemFont(ofSize: NSFont.systemFontSize(for: .small)) messageTextField.font = .systemFont(ofSize: NSFont.systemFontSize(for: .small))
panel.contentView?.addSubview(messageTextField) panel.contentView?.addSubview(messageTextField)
super.init(window: panel) super.init(window: panel)
} }
public required init?(coder: NSCoder) { public required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
@objc(showTooltip:atPoint:) @objc(showTooltip:atPoint:)
public func show(tooltip: String, at point: NSPoint) { public func show(tooltip: String, at point: NSPoint) {
messageTextField.textColor = TooltipController.textColor messageTextField.textColor = TooltipController.textColor
messageTextField.backgroundColor = TooltipController.backgroundColor messageTextField.backgroundColor = TooltipController.backgroundColor
self.tooltip = tooltip self.tooltip = tooltip
window?.orderFront(nil) window?.orderFront(nil)
set(windowLocation: point) set(windowLocation: point)
} }
@objc @objc
public func hide() { public func hide() {
window?.orderOut(nil) window?.orderOut(nil)
} }
private func set(windowLocation windowTopLeftPoint: NSPoint) { private func set(windowLocation windowTopLeftPoint: NSPoint) {
var adjustedPoint = windowTopLeftPoint var adjustedPoint = windowTopLeftPoint
adjustedPoint.y -= 5 adjustedPoint.y -= 5
var screenFrame = NSScreen.main?.visibleFrame ?? NSRect.zero var screenFrame = NSScreen.main?.visibleFrame ?? NSRect.zero
for screen in NSScreen.screens { for screen in NSScreen.screens {
let frame = screen.visibleFrame let frame = screen.visibleFrame
if windowTopLeftPoint.x >= frame.minX && if windowTopLeftPoint.x >= frame.minX && windowTopLeftPoint.x <= frame.maxX
windowTopLeftPoint.x <= frame.maxX && && windowTopLeftPoint.y >= frame.minY && windowTopLeftPoint.y <= frame.maxY
windowTopLeftPoint.y >= frame.minY && {
windowTopLeftPoint.y <= frame.maxY { screenFrame = frame
screenFrame = frame break
break }
} }
}
let windowSize = window?.frame.size ?? NSSize.zero let windowSize = window?.frame.size ?? NSSize.zero
// bottom beneath the screen? // bottom beneath the screen?
if adjustedPoint.y - windowSize.height < screenFrame.minY { if adjustedPoint.y - windowSize.height < screenFrame.minY {
adjustedPoint.y = screenFrame.minY + windowSize.height adjustedPoint.y = screenFrame.minY + windowSize.height
} }
// top over the screen? // top over the screen?
if adjustedPoint.y >= screenFrame.maxY { if adjustedPoint.y >= screenFrame.maxY {
adjustedPoint.y = screenFrame.maxY - 1.0 adjustedPoint.y = screenFrame.maxY - 1.0
} }
// right // right
if adjustedPoint.x + windowSize.width >= screenFrame.maxX { if adjustedPoint.x + windowSize.width >= screenFrame.maxX {
adjustedPoint.x = screenFrame.maxX - windowSize.width adjustedPoint.x = screenFrame.maxX - windowSize.width
} }
// left // left
if adjustedPoint.x < screenFrame.minX { if adjustedPoint.x < screenFrame.minX {
adjustedPoint.x = screenFrame.minX adjustedPoint.x = screenFrame.minX
} }
window?.setFrameTopLeftPoint(adjustedPoint) window?.setFrameTopLeftPoint(adjustedPoint)
} }
private func adjustSize() { private func adjustSize() {
let attrString = messageTextField.attributedStringValue; let attrString = messageTextField.attributedStringValue
var rect = attrString.boundingRect(with: NSSize(width: 1600.0, height: 1600.0), options: .usesLineFragmentOrigin) var rect = attrString.boundingRect(
rect.size.width += 10 with: NSSize(width: 1600.0, height: 1600.0), options: .usesLineFragmentOrigin)
messageTextField.frame = rect rect.size.width += 10
window?.setFrame(rect, display: true) messageTextField.frame = rect
} window?.setFrame(rect, display: true)
}
} }

View File

@ -1,51 +1,64 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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 Cocoa
@objc(AboutWindow) class ctlAboutWindow: NSWindowController { @objc(AboutWindow) class ctlAboutWindow: NSWindowController {
@IBOutlet weak var appVersionLabel: NSTextField! @IBOutlet weak var appVersionLabel: NSTextField!
@IBOutlet weak var appCopyrightLabel: NSTextField! @IBOutlet weak var appCopyrightLabel: NSTextField!
@IBOutlet var appEULAContent: NSTextView! @IBOutlet var appEULAContent: NSTextView!
override func windowDidLoad() { override func windowDidLoad() {
super.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)
}
@IBAction func btnWiki(_ sender: NSButton) { window?.standardWindowButton(.closeButton)?.isHidden = true
if let url = URL(string: "https://gitee.com/vchewing/vChewing-macOS/wikis") { window?.standardWindowButton(.miniaturizeButton)?.isHidden = true
NSWorkspace.shared.open(url) 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)
}
}
} }

View File

@ -1,118 +1,130 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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 Cocoa
@objc protocol ctlNonModalAlertWindowDelegate: AnyObject { @objc protocol ctlNonModalAlertWindowDelegate: AnyObject {
func ctlNonModalAlertWindowDidConfirm(_ controller: ctlNonModalAlertWindow) func ctlNonModalAlertWindowDidConfirm(_ controller: ctlNonModalAlertWindow)
func ctlNonModalAlertWindowDidCancel(_ controller: ctlNonModalAlertWindow) func ctlNonModalAlertWindowDidCancel(_ controller: ctlNonModalAlertWindow)
} }
class ctlNonModalAlertWindow: NSWindowController { class ctlNonModalAlertWindow: NSWindowController {
@objc(sharedInstance) @objc(sharedInstance)
static let shared = ctlNonModalAlertWindow(windowNibName: "frmNonModalAlertWindow") static let shared = ctlNonModalAlertWindow(windowNibName: "frmNonModalAlertWindow")
@IBOutlet weak var titleTextField: NSTextField! @IBOutlet weak var titleTextField: NSTextField!
@IBOutlet weak var contentTextField: NSTextField! @IBOutlet weak var contentTextField: NSTextField!
@IBOutlet weak var confirmButton: NSButton! @IBOutlet weak var confirmButton: NSButton!
@IBOutlet weak var cancelButton: NSButton! @IBOutlet weak var cancelButton: NSButton!
weak var delegate: ctlNonModalAlertWindowDelegate? weak var delegate: ctlNonModalAlertWindowDelegate?
@objc func show(title: String, content: String, confirmButtonTitle: String, cancelButtonTitle: String?, cancelAsDefault: Bool, delegate: ctlNonModalAlertWindowDelegate?) { @objc func show(
if window?.isVisible == true { title: String, content: String, confirmButtonTitle: String, cancelButtonTitle: String?,
self.delegate?.ctlNonModalAlertWindowDidCancel(self) cancelAsDefault: Bool, delegate: ctlNonModalAlertWindowDelegate?
} ) {
if window?.isVisible == true {
self.delegate?.ctlNonModalAlertWindowDidCancel(self)
}
self.delegate = delegate self.delegate = delegate
var oldFrame = confirmButton.frame var oldFrame = confirmButton.frame
confirmButton.title = confirmButtonTitle confirmButton.title = confirmButtonTitle
confirmButton.sizeToFit() confirmButton.sizeToFit()
var newFrame = confirmButton.frame var newFrame = confirmButton.frame
newFrame.size.width = max(90, newFrame.size.width + 10) newFrame.size.width = max(90, newFrame.size.width + 10)
newFrame.origin.x += oldFrame.size.width - newFrame.size.width newFrame.origin.x += oldFrame.size.width - newFrame.size.width
confirmButton.frame = newFrame confirmButton.frame = newFrame
if let cancelButtonTitle = cancelButtonTitle { if let cancelButtonTitle = cancelButtonTitle {
cancelButton.title = cancelButtonTitle cancelButton.title = cancelButtonTitle
cancelButton.sizeToFit() cancelButton.sizeToFit()
var adjustFrame = cancelButton.frame var adjustFrame = cancelButton.frame
adjustFrame.size.width = max(90, adjustFrame.size.width + 10) adjustFrame.size.width = max(90, adjustFrame.size.width + 10)
adjustFrame.origin.x = newFrame.origin.x - adjustFrame.size.width adjustFrame.origin.x = newFrame.origin.x - adjustFrame.size.width
confirmButton.frame = adjustFrame confirmButton.frame = adjustFrame
cancelButton.isHidden = false cancelButton.isHidden = false
} else { } else {
cancelButton.isHidden = true cancelButton.isHidden = true
} }
cancelButton.nextKeyView = confirmButton cancelButton.nextKeyView = confirmButton
confirmButton.nextKeyView = cancelButton confirmButton.nextKeyView = cancelButton
if cancelButtonTitle != nil { if cancelButtonTitle != nil {
if cancelAsDefault { if cancelAsDefault {
window?.defaultButtonCell = cancelButton.cell as? NSButtonCell window?.defaultButtonCell = cancelButton.cell as? NSButtonCell
} else { } else {
cancelButton.keyEquivalent = " " cancelButton.keyEquivalent = " "
window?.defaultButtonCell = confirmButton.cell as? NSButtonCell window?.defaultButtonCell = confirmButton.cell as? NSButtonCell
} }
} else { } else {
window?.defaultButtonCell = confirmButton.cell as? NSButtonCell window?.defaultButtonCell = confirmButton.cell as? NSButtonCell
} }
titleTextField.stringValue = title titleTextField.stringValue = title
oldFrame = contentTextField.frame oldFrame = contentTextField.frame
contentTextField.stringValue = content contentTextField.stringValue = content
var infiniteHeightFrame = oldFrame var infiniteHeightFrame = oldFrame
infiniteHeightFrame.size.width -= 4.0 infiniteHeightFrame.size.width -= 4.0
infiniteHeightFrame.size.height = 10240 infiniteHeightFrame.size.height = 10240
newFrame = (content as NSString).boundingRect(with: infiniteHeightFrame.size, options: [.usesLineFragmentOrigin], attributes: [.font: contentTextField.font!]) newFrame = (content as NSString).boundingRect(
newFrame.size.width = max(newFrame.size.width, oldFrame.size.width) with: infiniteHeightFrame.size, options: [.usesLineFragmentOrigin],
newFrame.size.height += 4.0 attributes: [.font: contentTextField.font!])
newFrame.origin = oldFrame.origin newFrame.size.width = max(newFrame.size.width, oldFrame.size.width)
newFrame.origin.y -= (newFrame.size.height - oldFrame.size.height) newFrame.size.height += 4.0
contentTextField.frame = newFrame newFrame.origin = oldFrame.origin
newFrame.origin.y -= (newFrame.size.height - oldFrame.size.height)
contentTextField.frame = newFrame
var windowFrame = window?.frame ?? NSRect.zero var windowFrame = window?.frame ?? NSRect.zero
windowFrame.size.height += (newFrame.size.height - oldFrame.size.height) windowFrame.size.height += (newFrame.size.height - oldFrame.size.height)
window?.level = NSWindow.Level(Int(CGShieldingWindowLevel()) + 1) window?.level = NSWindow.Level(Int(CGShieldingWindowLevel()) + 1)
window?.setFrame(windowFrame, display: true) window?.setFrame(windowFrame, display: true)
window?.center() window?.center()
window?.makeKeyAndOrderFront(self) window?.makeKeyAndOrderFront(self)
NSApp.activate(ignoringOtherApps: true) NSApp.activate(ignoringOtherApps: true)
} }
@IBAction func confirmButtonAction(_ sender: Any) { @IBAction func confirmButtonAction(_ sender: Any) {
delegate?.ctlNonModalAlertWindowDidConfirm(self) delegate?.ctlNonModalAlertWindowDidConfirm(self)
window?.orderOut(self) window?.orderOut(self)
} }
@IBAction func cancelButtonAction(_ sender: Any) { @IBAction func cancelButtonAction(_ sender: Any) {
cancel(sender) cancel(sender)
} }
func cancel(_ sender: Any) { func cancel(_ sender: Any) {
delegate?.ctlNonModalAlertWindowDidCancel(self) delegate?.ctlNonModalAlertWindowDidCancel(self)
delegate = nil delegate = nil
window?.orderOut(self) window?.orderOut(self)
} }
} }

View File

@ -1,279 +1,301 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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 Carbon
import Cocoa
// Extend the RangeReplaceableCollection to allow it clean duplicated characters. // Extend the RangeReplaceableCollection to allow it clean duplicated characters.
extension RangeReplaceableCollection where Element: Hashable { extension RangeReplaceableCollection where Element: Hashable {
var charDeDuplicate: Self { var charDeDuplicate: Self {
var set = Set<Element>() var set = Set<Element>()
return filter{ set.insert($0).inserted } return filter { set.insert($0).inserted }
} }
} }
// Please note that the class should be exposed using the same class name // 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 // in Objective-C in order to let IMK to see the same class name as
// the "InputMethodServerPreferencesWindowControllerClass" in Info.plist. // the "InputMethodServerPreferencesWindowControllerClass" in Info.plist.
@objc(ctlPrefWindow) class ctlPrefWindow: NSWindowController { @objc(ctlPrefWindow) class ctlPrefWindow: NSWindowController {
@IBOutlet weak var fontSizePopUpButton: NSPopUpButton! @IBOutlet weak var fontSizePopUpButton: NSPopUpButton!
@IBOutlet weak var uiLanguageButton: NSPopUpButton! @IBOutlet weak var uiLanguageButton: NSPopUpButton!
@IBOutlet weak var basisKeyboardLayoutButton: NSPopUpButton! @IBOutlet weak var basisKeyboardLayoutButton: NSPopUpButton!
@IBOutlet weak var selectionKeyComboBox: NSComboBox! @IBOutlet weak var selectionKeyComboBox: NSComboBox!
@IBOutlet weak var chkTrad2KangXi: NSButton! @IBOutlet weak var chkTrad2KangXi: NSButton!
@IBOutlet weak var chkTrad2JISShinjitai: NSButton! @IBOutlet weak var chkTrad2JISShinjitai: NSButton!
@IBOutlet weak var lblCurrentlySpecifiedUserDataFolder: NSTextFieldCell! @IBOutlet weak var lblCurrentlySpecifiedUserDataFolder: NSTextFieldCell!
var currentLanguageSelectItem: NSMenuItem? = nil
override func windowDidLoad() { var currentLanguageSelectItem: NSMenuItem? = nil
super.windowDidLoad()
lblCurrentlySpecifiedUserDataFolder.placeholderString = mgrLangModel.dataFolderPath(isDefaultFolder: true) override func windowDidLoad() {
super.windowDidLoad()
let languages = ["auto", "en", "zh-Hans", "zh-Hant", "ja"] lblCurrentlySpecifiedUserDataFolder.placeholderString = mgrLangModel.dataFolderPath(
var autoMUISelectItem: NSMenuItem? = nil isDefaultFolder: true)
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)
let list = TISCreateInputSourceList(nil, true).takeRetainedValue() as! [TISInputSource] let languages = ["auto", "en", "zh-Hans", "zh-Hant", "ja"]
var usKeyboardLayoutItem: NSMenuItem? = nil var autoMUISelectItem: NSMenuItem? = nil
var chosenBaseKeyboardLayoutItem: 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() if language == "auto" {
menuItem_AppleZhuyinBopomofo.title = String(format: NSLocalizedString("Apple Zhuyin Bopomofo", comment: "")) autoMUISelectItem = menuItem
menuItem_AppleZhuyinBopomofo.representedObject = String("com.apple.keylayout.ZhuyinBopomofo") }
basisKeyboardLayoutButton.menu?.addItem(menuItem_AppleZhuyinBopomofo)
let menuItem_AppleZhuyinEten = NSMenuItem() if !appleLanguages.isEmpty {
menuItem_AppleZhuyinEten.title = String(format: NSLocalizedString("Apple Zhuyin Eten", comment: "")) if appleLanguages[0] == language {
menuItem_AppleZhuyinEten.representedObject = String("com.apple.keylayout.ZhuyinEten") chosenLanguageItem = menuItem
basisKeyboardLayoutButton.menu?.addItem(menuItem_AppleZhuyinEten) }
}
uiLanguageButton.menu?.addItem(menuItem)
}
let basisKeyboardLayoutID = mgrPrefs.basisKeyboardLayout currentLanguageSelectItem = chosenLanguageItem ?? autoMUISelectItem
uiLanguageButton.select(currentLanguageSelectItem)
for source in list { let list = TISCreateInputSourceList(nil, true).takeRetainedValue() as! [TISInputSource]
if let categoryPtr = TISGetInputSourceProperty(source, kTISPropertyInputSourceCategory) { var usKeyboardLayoutItem: NSMenuItem? = nil
let category = Unmanaged<CFString>.fromOpaque(categoryPtr).takeUnretainedValue() var chosenBaseKeyboardLayoutItem: NSMenuItem? = nil
if category != kTISCategoryKeyboardInputSource {
continue
}
} else {
continue
}
if let asciiCapablePtr = TISGetInputSourceProperty(source, kTISPropertyInputSourceIsASCIICapable) { basisKeyboardLayoutButton.menu?.removeAllItems()
let asciiCapable = Unmanaged<CFBoolean>.fromOpaque(asciiCapablePtr).takeUnretainedValue()
if asciiCapable != kCFBooleanTrue {
continue
}
} else {
continue
}
if let sourceTypePtr = TISGetInputSourceProperty(source, kTISPropertyInputSourceType) { let itmAppleZhuyinBopomofo = NSMenuItem()
let sourceType = Unmanaged<CFString>.fromOpaque(sourceTypePtr).takeUnretainedValue() itmAppleZhuyinBopomofo.title = String(
if sourceType != kTISTypeKeyboardLayout { format: NSLocalizedString("Apple Zhuyin Bopomofo", comment: ""))
continue itmAppleZhuyinBopomofo.representedObject = String(
} "com.apple.keylayout.ZhuyinBopomofo")
} else { basisKeyboardLayoutButton.menu?.addItem(itmAppleZhuyinBopomofo)
continue
}
guard let sourceIDPtr = TISGetInputSourceProperty(source, kTISPropertyInputSourceID), let itmAppleZhuyinEten = NSMenuItem()
let localizedNamePtr = TISGetInputSourceProperty(source, kTISPropertyLocalizedName) else { itmAppleZhuyinEten.title = String(
continue format: NSLocalizedString("Apple Zhuyin Eten", comment: ""))
} itmAppleZhuyinEten.representedObject = String("com.apple.keylayout.ZhuyinEten")
basisKeyboardLayoutButton.menu?.addItem(itmAppleZhuyinEten)
let sourceID = String(Unmanaged<CFString>.fromOpaque(sourceIDPtr).takeUnretainedValue()) let basisKeyboardLayoutID = mgrPrefs.basisKeyboardLayout
let localizedName = String(Unmanaged<CFString>.fromOpaque(localizedNamePtr).takeUnretainedValue())
let menuItem = NSMenuItem() for source in list {
menuItem.title = localizedName if let categoryPtr = TISGetInputSourceProperty(source, kTISPropertyInputSourceCategory) {
menuItem.representedObject = sourceID let category = Unmanaged<CFString>.fromOpaque(categoryPtr).takeUnretainedValue()
if category != kTISCategoryKeyboardInputSource {
continue
}
} else {
continue
}
if sourceID == "com.apple.keylayout.US" { if let asciiCapablePtr = TISGetInputSourceProperty(
usKeyboardLayoutItem = menuItem source, kTISPropertyInputSourceIsASCIICapable)
} {
if basisKeyboardLayoutID == sourceID { let asciiCapable = Unmanaged<CFBoolean>.fromOpaque(asciiCapablePtr)
chosenBaseKeyboardLayoutItem = menuItem .takeUnretainedValue()
} if asciiCapable != kCFBooleanTrue {
basisKeyboardLayoutButton.menu?.addItem(menuItem) continue
} }
} else {
continue
}
switch basisKeyboardLayoutID { if let sourceTypePtr = TISGetInputSourceProperty(source, kTISPropertyInputSourceType) {
case "com.apple.keylayout.ZhuyinBopomofo": let sourceType = Unmanaged<CFString>.fromOpaque(sourceTypePtr).takeUnretainedValue()
chosenBaseKeyboardLayoutItem = menuItem_AppleZhuyinBopomofo if sourceType != kTISTypeKeyboardLayout {
case "com.apple.keylayout.ZhuyinEten": continue
chosenBaseKeyboardLayoutItem = menuItem_AppleZhuyinEten }
default: } else {
break // nothing to do continue
} }
basisKeyboardLayoutButton.select(chosenBaseKeyboardLayoutItem ?? usKeyboardLayoutItem) guard let sourceIDPtr = TISGetInputSourceProperty(source, kTISPropertyInputSourceID),
let localizedNamePtr = TISGetInputSourceProperty(source, kTISPropertyLocalizedName)
else {
continue
}
selectionKeyComboBox.usesDataSource = false let sourceID = String(Unmanaged<CFString>.fromOpaque(sourceIDPtr).takeUnretainedValue())
selectionKeyComboBox.removeAllItems() let localizedName = String(
selectionKeyComboBox.addItems(withObjectValues: mgrPrefs.suggestedCandidateKeys) Unmanaged<CFString>.fromOpaque(localizedNamePtr).takeUnretainedValue())
var candidateSelectionKeys = mgrPrefs.candidateKeys let menuItem = NSMenuItem()
if candidateSelectionKeys.isEmpty { menuItem.title = localizedName
candidateSelectionKeys = mgrPrefs.defaultCandidateKeys menuItem.representedObject = sourceID
}
selectionKeyComboBox.stringValue = candidateSelectionKeys if sourceID == "com.apple.keylayout.US" {
} usKeyboardLayoutItem = menuItem
}
if basisKeyboardLayoutID == sourceID {
chosenBaseKeyboardLayoutItem = menuItem
}
basisKeyboardLayoutButton.menu?.addItem(menuItem)
}
// CNS switch basisKeyboardLayoutID {
// case "com.apple.keylayout.ZhuyinBopomofo":
@IBAction func toggleCNSSupport(_ sender: Any) { chosenBaseKeyboardLayoutItem = itmAppleZhuyinBopomofo
mgrLangModel.setCNSEnabled(mgrPrefs.cns11643Enabled) case "com.apple.keylayout.ZhuyinEten":
} chosenBaseKeyboardLayoutItem = itmAppleZhuyinEten
default:
break // nothing to do
}
@IBAction func toggleSymbolInputEnabled(_ sender: Any) { basisKeyboardLayoutButton.select(chosenBaseKeyboardLayoutItem ?? usKeyboardLayoutItem)
mgrLangModel.setSymbolEnabled(mgrPrefs.symbolInputEnabled)
}
@IBAction func toggleTrad2KangXiAction(_ sender: Any) { selectionKeyComboBox.usesDataSource = false
if chkTrad2KangXi.state == .on && chkTrad2JISShinjitai.state == .on { selectionKeyComboBox.removeAllItems()
mgrPrefs.toggleShiftJISShinjitaiOutputEnabled() selectionKeyComboBox.addItems(withObjectValues: mgrPrefs.suggestedCandidateKeys)
}
}
@IBAction func toggleTrad2JISShinjitaiAction(_ sender: Any) { var candidateSelectionKeys = mgrPrefs.candidateKeys
if chkTrad2KangXi.state == .on && chkTrad2JISShinjitai.state == .on { if candidateSelectionKeys.isEmpty {
mgrPrefs.toggleChineseConversionEnabled() candidateSelectionKeys = mgrPrefs.defaultCandidateKeys
} }
}
@IBAction func updateBasisKeyboardLayoutAction(_ sender: Any) { selectionKeyComboBox.stringValue = candidateSelectionKeys
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)
}
}
@IBAction func clickedWhetherIMEShouldNotFartToggleAction(_ sender: Any) { // CNS
clsSFX.beep() //
} @IBAction func toggleCNSSupport(_ sender: Any) {
mgrLangModel.setCNSEnabled(mgrPrefs.cns11643Enabled)
}
@IBAction func changeSelectionKeyAction(_ sender: Any) { @IBAction func toggleSymbolInputEnabled(_ sender: Any) {
guard let keys = (sender as AnyObject).stringValue?.trimmingCharacters(in: .whitespacesAndNewlines).charDeDuplicate else { mgrLangModel.setSymbolEnabled(mgrPrefs.symbolInputEnabled)
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) { @IBAction func toggleTrad2KangXiAction(_ sender: Any) {
UserDefaults.standard.removeObject(forKey: "UserDataFolderSpecified") if chkTrad2KangXi.state == .on && chkTrad2JISShinjitai.state == .on {
IME.initLangModels(userOnly: true) mgrPrefs.toggleShiftJISShinjitaiOutputEnabled()
} }
}
@IBAction func chooseUserDataFolderToSpecify(_ sender: Any) { @IBAction func toggleTrad2JISShinjitaiAction(_ sender: Any) {
IME.dlgOpenPath.title = NSLocalizedString("Choose your desired user data folder.", comment: ""); if chkTrad2KangXi.state == .on && chkTrad2JISShinjitai.state == .on {
IME.dlgOpenPath.showsResizeIndicator = true; mgrPrefs.toggleChineseConversionEnabled()
IME.dlgOpenPath.showsHiddenFiles = true; }
IME.dlgOpenPath.canChooseFiles = false; }
IME.dlgOpenPath.canChooseDirectories = true;
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 { @IBAction func updateUiLanguageAction(_ sender: Any) {
IME.dlgOpenPath.beginSheetModal(for: self.window!) { result in if let selectItem = uiLanguageButton.selectedItem {
if result == NSApplication.ModalResponse.OK { if currentLanguageSelectItem == selectItem {
if (IME.dlgOpenPath.url != nil) { return
if (mgrLangModel.checkIfSpecifiedUserDataFolderValid(IME.dlgOpenPath.url!.path)) { }
mgrPrefs.userDataFolderSpecified = IME.dlgOpenPath.url!.path }
IME.initLangModels(userOnly: true) if let language = uiLanguageButton.selectedItem?.representedObject as? String {
} else { if language != "auto" {
clsSFX.beep() mgrPrefs.appleLanguages = [language]
if !PreviousFolderValidity { } else {
self.resetSpecifiedUserDataFolder(self) UserDefaults.standard.removeObject(forKey: "AppleLanguages")
} }
return
} NSLog("vChewing App self-terminated due to UI language change.")
} NSApplication.shared.terminate(nil)
} else { }
if !PreviousFolderValidity { }
self.resetSpecifiedUserDataFolder(self)
} @IBAction func clickedWhetherIMEShouldNotFartToggleAction(_ sender: Any) {
return clsSFX.beep()
} }
}
} // End If self.window != nil @IBAction func changeSelectionKeyAction(_ sender: Any) {
} // End IBAction 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
} }

View File

@ -7,8 +7,8 @@
<key>UpdateInfoSite</key> <key>UpdateInfoSite</key>
<string>https://gitee.com/vChewing/vChewing-macOS/releases</string> <string>https://gitee.com/vChewing/vChewing-macOS/releases</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1947</string> <string>1948</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.4.6</string> <string>1.4.7</string>
</dict> </dict>
</plist> </plist>

View File

@ -1,19 +1,25 @@
// Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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 Cocoa
@ -21,31 +27,31 @@ import Cocoa
@NSApplicationMain @NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate { class AppDelegate: NSObject, NSApplicationDelegate {
private var ctlAboutWindowInstance: ctlAboutWindow? // New About Window private var ctlAboutWindowInstance: ctlAboutWindow? // New About Window
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
}
func applicationWillTerminate(_ aNotification: Notification) { func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to tear down your application // Insert code here to initialize your application
} }
func applicationShouldTerminate(_ sender: NSApplication)-> NSApplication.TerminateReply { func applicationWillTerminate(_ aNotification: Notification) {
return .terminateNow // Insert code here to tear down your application
} }
// New About Window
@objc func showAbout() { func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {
if (ctlAboutWindowInstance == nil) { return .terminateNow
ctlAboutWindowInstance = ctlAboutWindow.init(windowNibName: "frmAboutWindow") }
} // New About Window
ctlAboutWindowInstance?.window?.center() @objc func showAbout() {
ctlAboutWindowInstance?.window?.orderFrontRegardless() // if ctlAboutWindowInstance == nil {
ctlAboutWindowInstance?.window?.level = .statusBar ctlAboutWindowInstance = ctlAboutWindow.init(windowNibName: "frmAboutWindow")
} }
// Call the New About Window ctlAboutWindowInstance?.window?.center()
@IBAction func about(_ sender: Any) { ctlAboutWindowInstance?.window?.orderFrontRegardless() //
(NSApp.delegate as? AppDelegate)?.showAbout() ctlAboutWindowInstance?.window?.level = .statusBar
NSApplication.shared.activate(ignoringOtherApps: true) }
} // Call the New About Window
@IBAction func about(_ sender: Any) {
(NSApp.delegate as? AppDelegate)?.showAbout()
NSApplication.shared.activate(ignoringOtherApps: true)
}
} }

View File

@ -1,41 +1,47 @@
// Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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 Cocoa
import Foundation
class Content: NSObject { class Content: NSObject {
@objc dynamic var contentString = "" @objc dynamic var contentString = ""
public init(contentString: String) { public init(contentString: String) {
self.contentString = contentString self.contentString = contentString
} }
} }
extension Content { extension Content {
func read(from data: Data) { func read(from data: Data) {
contentString = String(bytes: data, encoding: .utf8)! contentString = String(bytes: data, encoding: .utf8)!
} }
func data() -> Data? { func data() -> Data? {
return contentString.data(using: .utf8) return contentString.data(using: .utf8)
} }
} }

View File

@ -1,131 +1,145 @@
// Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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 Cocoa
class Document: NSDocument { class Document: NSDocument {
@objc var content = Content(contentString: "") @objc var content = Content(contentString: "")
var contentViewController: ViewController! var contentViewController: ViewController!
override init() { override init() {
super.init() super.init()
// Add your subclass-specific initialization here. // Add your subclass-specific initialization here.
} }
// MARK: - Enablers // MARK: - Enablers
// This enables auto save. // This enables auto save.
override class var autosavesInPlace: Bool { override class var autosavesInPlace: Bool {
return true return true
} }
// This enables asynchronous-writing. // This enables asynchronous-writing.
override func canAsynchronouslyWrite(to url: URL, ofType typeName: String, for saveOperation: NSDocument.SaveOperationType) -> Bool { override func canAsynchronouslyWrite(
return true 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" // This enables asynchronous reading.
} override class func canConcurrentlyReadDocuments(ofType: String) -> Bool {
return ofType == "public.plain-text"
// MARK: - User Interface }
/// - Tag: makeWindowControllersExample // MARK: - User Interface
override func makeWindowControllers() {
// Returns the storyboard that contains your document window. /// - Tag: makeWindowControllersExample
let storyboard = NSStoryboard(name: NSStoryboard.Name("Main"), bundle: nil) override func makeWindowControllers() {
if let windowController = // Returns the storyboard that contains your document window.
storyboard.instantiateController( let storyboard = NSStoryboard(name: NSStoryboard.Name("Main"), bundle: nil)
withIdentifier: NSStoryboard.SceneIdentifier("Document Window Controller")) as? NSWindowController { if let windowController =
addWindowController(windowController) storyboard.instantiateController(
withIdentifier: NSStoryboard.SceneIdentifier("Document Window Controller"))
// Set the view controller's represented object as your document. as? NSWindowController
if let contentVC = windowController.contentViewController as? ViewController { {
contentVC.representedObject = content addWindowController(windowController)
contentViewController = contentVC
} // 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) // MARK: - Reading and Writing
strToDealWith.formatConsolidate(HYPY2BPMF: false)
let processedIncomingData = Data(strToDealWith.utf8) /// - Tag: readExample
content.read(from: processedIncomingData) override func read(from data: Data, ofType typeName: String) throws {
} var strToDealWith = String(decoding: data, as: UTF8.self)
strToDealWith.formatConsolidate(cnvHYPYtoBPMF: false)
/// - Tag: writeExample let processedIncomingData = Data(strToDealWith.utf8)
override func data(ofType typeName: String) throws -> Data { content.read(from: processedIncomingData)
var strToDealWith = content.contentString }
strToDealWith.formatConsolidate(HYPY2BPMF: true)
let outputData = Data(strToDealWith.utf8) /// - Tag: writeExample
return outputData override func data(ofType typeName: String) throws -> Data {
} var strToDealWith = content.contentString
strToDealWith.formatConsolidate(cnvHYPYtoBPMF: true)
// MARK: - Printing let outputData = Data(strToDealWith.utf8)
return outputData
func thePrintInfo() -> NSPrintInfo { }
let thePrintInfo = NSPrintInfo()
thePrintInfo.horizontalPagination = .fit // MARK: - Printing
thePrintInfo.isHorizontallyCentered = false
thePrintInfo.isVerticallyCentered = false func thePrintInfo() -> NSPrintInfo {
let thePrintInfo = NSPrintInfo()
// One inch margin all the way around. thePrintInfo.horizontalPagination = .fit
thePrintInfo.leftMargin = 72.0 thePrintInfo.isHorizontallyCentered = false
thePrintInfo.rightMargin = 72.0 thePrintInfo.isVerticallyCentered = false
thePrintInfo.topMargin = 72.0
thePrintInfo.bottomMargin = 72.0 // One inch margin all the way around.
thePrintInfo.leftMargin = 72.0
printInfo.dictionary().setObject(NSNumber(value: true), thePrintInfo.rightMargin = 72.0
forKey: NSPrintInfo.AttributeKey.headerAndFooter as NSCopying) thePrintInfo.topMargin = 72.0
thePrintInfo.bottomMargin = 72.0
return thePrintInfo
} printInfo.dictionary().setObject(
NSNumber(value: true),
@objc forKey: NSPrintInfo.AttributeKey.headerAndFooter as NSCopying)
func printOperationDidRun(
_ printOperation: NSPrintOperation, success: Bool, contextInfo: UnsafeMutableRawPointer?) { return thePrintInfo
// Printing finished... }
}
@objc
@IBAction override func printDocument(_ sender: Any?) { func printOperationDidRun(
// Print the NSTextView. _ printOperation: NSPrintOperation, success: Bool, contextInfo: UnsafeMutableRawPointer?
) {
// Create a copy to manipulate for printing. // Printing finished...
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))
@IBAction override func printDocument(_ sender: Any?) {
// Make sure we print on a white background. // Print the NSTextView.
textView.appearance = NSAppearance(named: .aqua)
// Create a copy to manipulate for printing.
// Copy the attributed string. let pageSize = NSSize(
textView.textStorage?.append(NSAttributedString(string: content.contentString)) width: (printInfo.paperSize.width), height: (printInfo.paperSize.height))
let textView = NSTextView(
let printOperation = NSPrintOperation(view: textView) frame: NSRect(x: 0.0, y: 0.0, width: pageSize.width, height: pageSize.height))
printOperation.runModal(
for: windowControllers[0].window!, // Make sure we print on a white background.
delegate: self, textView.appearance = NSAppearance(named: .aqua)
didRun: #selector(printOperationDidRun(_:success:contextInfo:)), contextInfo: nil)
} // 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)
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,59 +1,65 @@
// Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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 Cocoa
class ViewController: NSViewController, NSTextViewDelegate { class ViewController: NSViewController, NSTextViewDelegate {
/// - Tag: setRepresentedObjectExample /// - Tag: setRepresentedObjectExample
override var representedObject: Any? { override var representedObject: Any? {
didSet { didSet {
// Pass down the represented object to all of the child view controllers. // Pass down the represented object to all of the child view controllers.
for child in children { for child in children {
child.representedObject = representedObject child.representedObject = representedObject
} }
} }
} }
weak var document: Document? { weak var document: Document? {
if let docRepresentedObject = representedObject as? Document { if let docRepresentedObject = representedObject as? Document {
return docRepresentedObject return docRepresentedObject
} }
return nil return nil
} }
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
// Do any additional setup after loading the view. // Do any additional setup after loading the view.
} }
override func viewDidAppear() { override func viewDidAppear() {
super.viewDidAppear() super.viewDidAppear()
} }
// MARK: - NSTextViewDelegate // MARK: - NSTextViewDelegate
func textDidBeginEditing(_ notification: Notification) { func textDidBeginEditing(_ notification: Notification) {
document?.objectDidBeginEditing(self) document?.objectDidBeginEditing(self)
} }
func textDidEndEditing(_ notification: Notification) { func textDidEndEditing(_ notification: Notification) {
document?.objectDidEndEditing(self) document?.objectDidEndEditing(self)
} }
} }

View File

@ -1,35 +1,41 @@
// Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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 Cocoa
class WindowController: NSWindowController, NSWindowDelegate { class WindowController: NSWindowController, NSWindowDelegate {
override func windowDidLoad() { override func windowDidLoad() {
super.windowDidLoad() super.windowDidLoad()
} }
required init?(coder aDecoder: NSCoder) { required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder) super.init(coder: aDecoder)
/** NSWindows loaded from the storyboard will be cascaded /** NSWindows loaded from the storyboard will be cascaded
based on the original frame of the window in the storyboard. based on the original frame of the window in the storyboard.
*/ */
shouldCascadeWindows = true shouldCascadeWindows = true
} }
} }

View File

@ -1,51 +1,64 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // 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 Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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 Cocoa
@objc(AboutWindow) class ctlAboutWindow: NSWindowController { @objc(AboutWindow) class ctlAboutWindow: NSWindowController {
@IBOutlet weak var appVersionLabel: NSTextField! @IBOutlet weak var appVersionLabel: NSTextField!
@IBOutlet weak var appCopyrightLabel: NSTextField! @IBOutlet weak var appCopyrightLabel: NSTextField!
@IBOutlet var appEULAContent: NSTextView! @IBOutlet var appEULAContent: NSTextView!
override func windowDidLoad() { override func windowDidLoad() {
super.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)
}
@IBAction func btnWiki(_ sender: NSButton) { window?.standardWindowButton(.closeButton)?.isHidden = true
if let url = URL(string: "https://gitee.com/vchewing/vChewing-macOS/wikis") { window?.standardWindowButton(.miniaturizeButton)?.isHidden = true
NSWorkspace.shared.open(url) 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)
}
}
} }

Some files were not shown because too many files have changed in this diff Show More