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

215
.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/*
StyleCopReport.xml
tarballs/
Temporary Items
test-results/
TestResult.xml
Thumbs.db
UpgradeLog*.htm
UpgradeLog*.XML
x64/
x86/
xcuserdata 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,44 +1,54 @@
#!/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(
pattern: pattern, options: [.caseInsensitive, .anchorsMatchLines])
let range = NSRange(self.startIndex..., in: self) let range = NSRange(self.startIndex..., in: self)
self = regex.stringByReplacingMatches(in: self, options: [], range: range, withTemplate: replaceWith) self = regex.stringByReplacingMatches(
in: self, options: [], range: range, withTemplate: replaceWith)
} catch { return } } 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
} }
@ -72,35 +82,35 @@ struct Entry {
// 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 {
@ -111,8 +121,7 @@ func rawDictForPhrases(isCHS: Bool) -> [Entry] {
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 []
} }
@ -127,7 +136,8 @@ func rawDictForPhrases(isCHS: Bool) -> [Entry] {
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(NSOrderedSet(array: strRAW.components(separatedBy: "\n")).array as! [String]) let arrData = Array(
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: " ")
@ -159,7 +169,11 @@ func rawDictForPhrases(isCHS: Bool) -> [Entry] {
} }
} }
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,
valCount: occurrence)
]
} }
} }
NSLog(" - \(i18n): 成功生成詞語語料辭典(權重待計算)。") NSLog(" - \(i18n): 成功生成詞語語料辭典(權重待計算)。")
@ -175,8 +189,7 @@ func rawDictForKanjis(isCHS: Bool) -> [Entry] {
// //
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 []
} }
@ -191,12 +204,17 @@ func rawDictForKanjis(isCHS: Bool) -> [Entry] {
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(NSOrderedSet(array: strRAW.components(separatedBy: "\n")).array as! [String]) let arrData = Array(
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(
separator: "\t")
let varLineDataPost = lineData.components(separatedBy: " ").suffix(isCHS ? 1 : 2)
.joined(
separator: "\t")
varLineData = varLineDataPre + "\t" + varLineDataPost varLineData = varLineDataPre + "\t" + varLineDataPost
let arrLineData = varLineData.components(separatedBy: " ") let arrLineData = varLineData.components(separatedBy: " ")
var varLineDataProcessed: String = "" var varLineDataProcessed: String = ""
@ -227,7 +245,11 @@ func rawDictForKanjis(isCHS: Bool) -> [Entry] {
} }
} }
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,
valCount: occurrence)
]
} }
} }
NSLog(" - \(i18n): 成功生成單字語料辭典(權重待計算)。") NSLog(" - \(i18n): 成功生成單字語料辭典(權重待計算)。")
@ -245,8 +267,7 @@ func rawDictForNonKanjis(isCHS: Bool) -> [Entry] {
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 []
} }
@ -261,12 +282,14 @@ func rawDictForNonKanjis(isCHS: Bool) -> [Entry] {
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(NSOrderedSet(array: strRAW.components(separatedBy: "\n")).array as! [String]) let arrData = Array(
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(
separator: "\t") //
let arrLineData = varLineData.components(separatedBy: " ") let arrLineData = varLineData.components(separatedBy: " ")
var varLineDataProcessed: String = "" var varLineDataProcessed: String = ""
var count = 0 var count = 0
@ -296,7 +319,11 @@ func rawDictForNonKanjis(isCHS: Bool) -> [Entry] {
} }
} }
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,
valCount: occurrence)
]
} }
} }
NSLog(" - \(i18n): 成功生成非漢字語料辭典(權重待計算)。") NSLog(" - \(i18n): 成功生成非漢字語料辭典(權重待計算)。")
@ -310,7 +337,8 @@ func weightAndSort(_ arrStructUncalculated: [Entry], isCHS: Bool) -> [Entry] {
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
@ -324,30 +352,39 @@ func weightAndSort(_ arrStructUncalculated: [Entry], isCHS: Bool) -> [Entry] {
case -1: // case -1: //
weight = -13 weight = -13
case 0: // case 0: //
weight = log10(fscale**(Float(entry.valPhrase.count) / 3.0 - 1.0) * 0.5 / norm) weight = log10(
fscale ** (Float(entry.valPhrase.count) / 3.0 - 1.0) * 0.5 / norm)
default: default:
weight = log10(fscale**(Float(entry.valPhrase.count) / 3.0 - 1.0) * Float(entry.valCount) / norm) // Credit: MJHsieh. weight = log10(
fscale ** (Float(entry.valPhrase.count) / 3.0 - 1.0)
* Float(entry.valCount) / norm) // Credit: MJHsieh.
} }
let weightRounded: Float = weight.rounded(toPlaces: 3) // let weightRounded: Float = weight.rounded(toPlaces: 3) //
arrStructCalculated += [Entry.init(valPhone: entry.valPhone, valPhrase: entry.valPhrase, valWeight: weightRounded, valCount: entry.valCount)] arrStructCalculated += [
Entry.init(
valPhone: entry.valPhone, valPhrase: entry.valPhrase, valWeight: weightRounded,
valCount: entry.valCount)
]
} }
NSLog(" - \(i18n): 成功計算權重。") NSLog(" - \(i18n): 成功計算權重。")
// ========================================== // ==========================================
// //
let arrStructSorted: [Entry] = arrStructCalculated.sorted(by: {(lhs, rhs) -> Bool in return (lhs.valPhone, rhs.valCount) < (rhs.valPhone, lhs.valCount)}) let arrStructSorted: [Entry] = arrStructCalculated.sorted(by: { (lhs, rhs) -> Bool in
return (lhs.valPhone, rhs.valCount) < (rhs.valPhone, lhs.valCount)
})
NSLog(" - \(i18n): 排序整理完畢,準備編譯要寫入的檔案內容。") NSLog(" - \(i18n): 排序整理完畢,準備編譯要寫入的檔案內容。")
return arrStructSorted 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(
isCHS ? urlOutputCHS : urlOutputCHT)
var strPrintLine = "" var strPrintLine = ""
// //
do { do {
strPrintLine += try String(contentsOfFile: urlPunctuation, encoding: .utf8) 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): 成功插入標點符號與西文字母數據。")
@ -360,13 +397,14 @@ func fileOutput(isCHS: Bool) {
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)
+ "\n"
} }
NSLog(" - \(i18n): 要寫入檔案的內容編譯完畢。") NSLog(" - \(i18n): 要寫入檔案的內容編譯完畢。")
do { do {
try strPrintLine.write(to: pathOutput, atomically: false, encoding: .utf8) try strPrintLine.write(to: pathOutput, atomically: false, encoding: .utf8)
} } catch {
catch {
NSLog(" - \(i18n): Error on writing strings to file: \(error)") NSLog(" - \(i18n): Error on writing strings to file: \(error)")
} }
NSLog(" - \(i18n): 寫入完成。") NSLog(" - \(i18n): 寫入完成。")

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
@ -61,8 +70,11 @@ class AppDelegate: NSWindowController, NSApplicationDelegate {
} }
func applicationDidFinishLaunching(_ notification: Notification) { func applicationDidFinishLaunching(_ notification: Notification) {
guard let installingVersion = Bundle.main.infoDictionary?[kCFBundleVersionKey as String] as? String, guard
let versionString = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String else { let installingVersion = Bundle.main.infoDictionary?[kCFBundleVersionKey as String]
as? String,
let versionString = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
else {
return return
} }
self.installingVersion = installingVersion self.installingVersion = installingVersion
@ -75,25 +87,36 @@ class AppDelegate: NSWindowController, NSApplicationDelegate {
window?.defaultButtonCell = cell window?.defaultButtonCell = cell
} }
if let copyrightLabel = Bundle.main.localizedInfoDictionary?["NSHumanReadableCopyright"] as? String { if let copyrightLabel = Bundle.main.localizedInfoDictionary?["NSHumanReadableCopyright"]
as? String
{
appCopyrightLabel.stringValue = copyrightLabel appCopyrightLabel.stringValue = copyrightLabel
} }
if let eulaContent = Bundle.main.localizedInfoDictionary?["CFEULAContent"] as? String { if let eulaContent = Bundle.main.localizedInfoDictionary?["CFEULAContent"] as? String {
appEULAContent.string = eulaContent appEULAContent.string = eulaContent
} }
appVersionLabel.stringValue = String(format: "%@ Build %@", versionString, installingVersion) appVersionLabel.stringValue = String(
format: "%@ Build %@", versionString, installingVersion)
window?.title = String(format: NSLocalizedString("%@ (for version %@, r%@)", comment: ""), window?.title ?? "", versionString, installingVersion) window?.title = String(
format: NSLocalizedString("%@ (for version %@, r%@)", comment: ""), window?.title ?? "",
versionString, installingVersion)
window?.standardWindowButton(.closeButton)?.isHidden = true window?.standardWindowButton(.closeButton)?.isHidden = true
window?.standardWindowButton(.miniaturizeButton)?.isHidden = true window?.standardWindowButton(.miniaturizeButton)?.isHidden = true
window?.standardWindowButton(.zoomButton)?.isHidden = true window?.standardWindowButton(.zoomButton)?.isHidden = true
if FileManager.default.fileExists(atPath: (kTargetPartialPath as NSString).expandingTildeInPath) { if FileManager.default.fileExists(
atPath: (kTargetPartialPath as NSString).expandingTildeInPath)
{
let currentBundle = Bundle(path: (kTargetPartialPath as NSString).expandingTildeInPath) let currentBundle = Bundle(path: (kTargetPartialPath as NSString).expandingTildeInPath)
let shortVersion = currentBundle?.infoDictionary?["CFBundleShortVersionString"] as? String let shortVersion =
let currentVersion = currentBundle?.infoDictionary?[kCFBundleVersionKey as String] as? String currentBundle?.infoDictionary?["CFBundleShortVersionString"] as? String
let currentVersion =
currentBundle?.infoDictionary?[kCFBundleVersionKey as String] as? String
currentVersionNumber = (currentVersion as NSString?)?.integerValue ?? 0 currentVersionNumber = (currentVersion as NSString?)?.integerValue ?? 0
if shortVersion != nil, let currentVersion = currentVersion, currentVersion.compare(installingVersion, options: .numeric) == .orderedAscending { if shortVersion != nil, let currentVersion = currentVersion,
currentVersion.compare(installingVersion, options: .numeric) == .orderedAscending
{
upgrading = true upgrading = true
} }
} }
@ -126,12 +149,18 @@ class AppDelegate: NSWindowController, NSApplicationDelegate {
} }
func removeThenInstallInputMethod() { func removeThenInstallInputMethod() {
if FileManager.default.fileExists(atPath: (kTargetPartialPath as NSString).expandingTildeInPath) == false { if FileManager.default.fileExists(
self.installInputMethod(previousExists: false, previousVersionNotFullyDeactivatedWarning: false) atPath: (kTargetPartialPath as NSString).expandingTildeInPath)
== false
{
self.installInputMethod(
previousExists: false, previousVersionNotFullyDeactivatedWarning: false)
return return
} }
let shouldWaitForTranslocationRemoval = appBundleChronoshiftedToARandomizedPath(kTargetPartialPath) && (window?.responds(to: #selector(NSWindow.beginSheet(_:completionHandler:))) ?? false) let shouldWaitForTranslocationRemoval =
appBundleChronoshiftedToARandomizedPath(kTargetPartialPath)
&& (window?.responds(to: #selector(NSWindow.beginSheet(_:completionHandler:))) ?? false)
// //
do { do {
@ -148,8 +177,7 @@ class AppDelegate: NSWindowController, NSApplicationDelegate {
NSLog("File does not exist") NSLog("File does not exist")
} }
} } catch let error as NSError {
catch let error as NSError {
NSLog("An error took place: \(error)") NSLog("An error took place: \(error)")
} }
@ -164,32 +192,47 @@ class AppDelegate: NSWindowController, NSApplicationDelegate {
window?.beginSheet(progressSheet) { returnCode in window?.beginSheet(progressSheet) { returnCode in
DispatchQueue.main.async { DispatchQueue.main.async {
if returnCode == .continue { if returnCode == .continue {
self.installInputMethod(previousExists: true, previousVersionNotFullyDeactivatedWarning: false) self.installInputMethod(
previousExists: true,
previousVersionNotFullyDeactivatedWarning: false)
} else { } else {
self.installInputMethod(previousExists: true, previousVersionNotFullyDeactivatedWarning: true) self.installInputMethod(
previousExists: true,
previousVersionNotFullyDeactivatedWarning: true)
} }
} }
} }
translocationRemovalStartTime = Date() translocationRemovalStartTime = Date()
Timer.scheduledTimer(timeInterval: kTranslocationRemovalTickInterval, target: self, selector: #selector(timerTick(_:)), userInfo: nil, repeats: true) 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
) {
guard
let targetBundle = archiveUtil?.unzipNotarizedArchive()
?? Bundle.main.path(forResource: kTargetBin, ofType: kTargetType)
else {
return return
} }
let cpTask = Process() let cpTask = Process()
cpTask.launchPath = "/bin/cp" cpTask.launchPath = "/bin/cp"
cpTask.arguments = ["-R", targetBundle, (kDestinationPartial as NSString).expandingTildeInPath] cpTask.arguments = [
"-R", targetBundle, (kDestinationPartial as NSString).expandingTildeInPath,
]
cpTask.launch() cpTask.launch()
cpTask.waitUntilExit() cpTask.waitUntilExit()
if cpTask.terminationStatus != 0 { if cpTask.terminationStatus != 0 {
runAlertPanel(title: NSLocalizedString("Install Failed", comment: ""), runAlertPanel(
title: NSLocalizedString("Install Failed", comment: ""),
message: NSLocalizedString("Cannot copy the file to the destination.", comment: ""), message: NSLocalizedString("Cannot copy the file to the destination.", comment: ""),
buttonTitle: NSLocalizedString("Cancel", comment: "")) buttonTitle: NSLocalizedString("Cancel", comment: ""))
endAppWithDelay() endAppWithDelay()
@ -206,28 +249,38 @@ class AppDelegate: NSWindowController, NSApplicationDelegate {
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(
"Cannot find input source %@ after registration.", comment: ""),
imeIdentifier)
runAlertPanel(
title: NSLocalizedString("Fatal Error", comment: ""), message: message,
buttonTitle: NSLocalizedString("Abort", comment: ""))
endAppWithDelay() endAppWithDelay()
return 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+,
@ -238,10 +291,10 @@ class AppDelegate: NSWindowController, NSApplicationDelegate {
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)")
} }
} }
@ -249,16 +302,22 @@ class AppDelegate: NSWindowController, NSApplicationDelegate {
let ntfPostInstall = NSAlert() let ntfPostInstall = NSAlert()
if warning { if warning {
ntfPostInstall.messageText = NSLocalizedString("Attention", comment: "") 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.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: "")) ntfPostInstall.addButton(withTitle: NSLocalizedString("OK", comment: ""))
} else { } else {
if !mainInputSourceEnabled && !isMacOS12OrAbove { if !mainInputSourceEnabled && !isMacOS12OrAbove {
ntfPostInstall.messageText = NSLocalizedString("Warning", comment: "") 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.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: "")) ntfPostInstall.addButton(withTitle: NSLocalizedString("Continue", comment: ""))
} else { } else {
ntfPostInstall.messageText = NSLocalizedString("Installation Successful", comment: "") ntfPostInstall.messageText = NSLocalizedString(
ntfPostInstall.informativeText = NSLocalizedString("vChewing is ready to use.", comment: "") "Installation Successful", comment: "")
ntfPostInstall.informativeText = NSLocalizedString(
"vChewing is ready to use.", comment: "")
ntfPostInstall.addButton(withTitle: NSLocalizedString("OK", comment: "")) ntfPostInstall.addButton(withTitle: NSLocalizedString("OK", comment: ""))
} }
} }
@ -277,9 +336,8 @@ class AppDelegate: NSWindowController, NSApplicationDelegate {
NSApp.terminate(self) NSApp.terminate(self)
} }
func windowWillClose(_ Notification: Notification) { func windowWillClose(_ notification: Notification) {
NSApp.terminate(self) NSApp.terminate(self)
} }
} }

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
@ -35,7 +42,8 @@ struct ArchiveUtil {
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(
atPath: notarizedArchivesPath)
else { else {
return false return false
} }
@ -50,8 +58,9 @@ struct ArchiveUtil {
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), "
+ "archive exists: \(notarizedArchiveExists), devMode app bundle exists: \(devModeAppBundleExists)"
alert.addButton(withTitle: "Terminate") alert.addButton(withTitle: "Terminate")
alert.runModal() alert.runModal()
NSApp.terminate(nil) NSApp.terminate(nil)
@ -78,10 +87,12 @@ struct ArchiveUtil {
return nil return nil
} }
guard let notarizedArchive = notarizedArchive, guard let notarizedArchive = notarizedArchive,
let resourcePath = Bundle.main.resourcePath else { let resourcePath = Bundle.main.resourcePath
else {
return nil return nil
} }
let tempFilePath = (NSTemporaryDirectory() as NSString).appendingPathComponent(UUID().uuidString) let tempFilePath = (NSTemporaryDirectory() as NSString).appendingPathComponent(
UUID().uuidString)
let arguments: [String] = [notarizedArchive, "-d", tempFilePath] let arguments: [String] = [notarizedArchive, "-d", tempFilePath]
let unzipTask = Process() let unzipTask = Process()
unzipTask.launchPath = "/usr/bin/unzip" unzipTask.launchPath = "/usr/bin/unzip"
@ -92,7 +103,9 @@ struct ArchiveUtil {
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(
FileManager.default.fileExists(atPath: result),
"App bundle must be unzipped at \(result).")
return result return result
} }
@ -100,17 +113,21 @@ struct ArchiveUtil {
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(
"NotarizedArchives")
return notarizedArchivesPath 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]
as? String
else {
return nil return nil
} }
let notarizedArchiveBasename = "\(appName)-r\(bundleVersion).zip" let notarizedArchiveBasename = "\(appName)-r\(bundleVersion).zip"
let notarizedArchive = (notarizedArchivesPath as NSString).appendingPathComponent(notarizedArchiveBasename) let notarizedArchive = (notarizedArchivesPath as NSString).appendingPathComponent(
notarizedArchiveBasename)
return notarizedArchive 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

@ -7,7 +7,7 @@ let package = Package(
products: [ products: [
.library( .library(
name: "OpenCC", name: "OpenCC",
targets: ["OpenCC"]), targets: ["OpenCC"])
], ],
targets: [ targets: [
.target( .target(

View File

@ -22,7 +22,11 @@ extension ChineseConverter {
} }
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
let path = bundle.path(
forResource: name.description, ofType: "ocd2",
inDirectory: DictionaryLoader.subdirectory)
else {
throw ConversionError.fileNotFound throw ConversionError.fileNotFound
} }
return try DictionaryLoader.dictCache.value(for: path) { return try DictionaryLoader.dictCache.value(for: path) {

View File

@ -1,4 +1,5 @@
import XCTest import XCTest
@testable import OpenCC @testable import OpenCC
let testCases: [(String, ChineseConverter.Options)] = [ let testCases: [(String, ChineseConverter.Options)] = [
@ -20,7 +21,8 @@ class OpenCCTests: XCTestCase {
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(
forResource: name, withExtension: ext, subdirectory: "testcases")!
return try! String(contentsOf: url) return try! String(contentsOf: url)
} }
for (name, opt) in testCases { for (name, opt) in testCases {
@ -54,7 +56,8 @@ class OpenCCTests: XCTestCase {
func testConversionPerformance() throws { func testConversionPerformance() throws {
let cov = try converter(option: [.traditionalize, .twStandard, .twIdiom]) let cov = try converter(option: [.traditionalize, .twStandard, .twIdiom])
let url = Bundle.module.url(forResource: "zuozhuan", withExtension: "txt", subdirectory: "benchmark")! let url = Bundle.module.url(
forResource: "zuozhuan", withExtension: "txt", subdirectory: "benchmark")!
// 1.9 MB, 624k word // 1.9 MB, 624k word
let str = try String(contentsOf: url) let str = try String(contentsOf: url)
measure { measure {

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

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 "Mandarin.h" #include "Mandarin.h"
@ -22,19 +29,22 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR TH
#include <algorithm> #include <algorithm>
#include <cctype> #include <cctype>
namespace Mandarin { namespace Mandarin
{
class PinyinParseHelper { class PinyinParseHelper
{
public: public:
static const bool ConsumePrefix(std::string& target, static const bool ConsumePrefix(std::string &target, const std::string &prefix)
const std::string& prefix) { {
if (target.length() < prefix.length()) { if (target.length() < prefix.length())
{
return false; return false;
} }
if (target.substr(0, prefix.length()) == prefix) { if (target.substr(0, prefix.length()) == prefix)
target = {
target.substr(prefix.length(), target.length() - prefix.length()); target = target.substr(prefix.length(), target.length() - prefix.length());
return true; return true;
} }
@ -42,7 +52,8 @@ public:
} }
}; };
class BopomofoCharacterMap { class BopomofoCharacterMap
{
public: public:
static const BopomofoCharacterMap &SharedInstance(); static const BopomofoCharacterMap &SharedInstance();
@ -53,8 +64,10 @@ protected:
BopomofoCharacterMap(); BopomofoCharacterMap();
}; };
const BPMF BPMF::FromHanyuPinyin(const std::string& str) { const BPMF BPMF::FromHanyuPinyin(const std::string &str)
if (!str.length()) { {
if (!str.length())
{
return BPMF(); return BPMF();
} }
@ -70,35 +83,53 @@ const BPMF BPMF::FromHanyuPinyin(const std::string& str) {
bool independentConsonant = false; bool independentConsonant = false;
// the y exceptions fist // the y exceptions fist
if (0) { if (0)
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "yuan")) { {
}
else if (PinyinParseHelper::ConsumePrefix(pinyin, "yuan"))
{
secondComponent = BPMF::UE; secondComponent = BPMF::UE;
thirdComponent = BPMF::AN; thirdComponent = BPMF::AN;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "ying")) { }
else if (PinyinParseHelper::ConsumePrefix(pinyin, "ying"))
{
secondComponent = BPMF::I; secondComponent = BPMF::I;
thirdComponent = BPMF::ENG; thirdComponent = BPMF::ENG;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "yung")) { }
else if (PinyinParseHelper::ConsumePrefix(pinyin, "yung"))
{
secondComponent = BPMF::UE; secondComponent = BPMF::UE;
thirdComponent = BPMF::ENG; thirdComponent = BPMF::ENG;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "yong")) { }
else if (PinyinParseHelper::ConsumePrefix(pinyin, "yong"))
{
secondComponent = BPMF::UE; secondComponent = BPMF::UE;
thirdComponent = BPMF::ENG; thirdComponent = BPMF::ENG;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "yue")) { }
else if (PinyinParseHelper::ConsumePrefix(pinyin, "yue"))
{
secondComponent = BPMF::UE; secondComponent = BPMF::UE;
thirdComponent = BPMF::E; thirdComponent = BPMF::E;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "yun")) { }
else if (PinyinParseHelper::ConsumePrefix(pinyin, "yun"))
{
secondComponent = BPMF::UE; secondComponent = BPMF::UE;
thirdComponent = BPMF::EN; thirdComponent = BPMF::EN;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "you")) { }
else if (PinyinParseHelper::ConsumePrefix(pinyin, "you"))
{
secondComponent = BPMF::I; secondComponent = BPMF::I;
thirdComponent = BPMF::OU; thirdComponent = BPMF::OU;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "yu")) { }
else if (PinyinParseHelper::ConsumePrefix(pinyin, "yu"))
{
secondComponent = BPMF::UE; secondComponent = BPMF::UE;
} }
// try the first character // try the first character
char c = pinyin.length() ? pinyin[0] : 0; char c = pinyin.length() ? pinyin[0] : 0;
switch (c) { switch (c)
{
case 'b': case 'b':
firstComponent = BPMF::B; firstComponent = BPMF::B;
pinyin = pinyin.substr(1); pinyin = pinyin.substr(1);
@ -162,7 +193,8 @@ const BPMF BPMF::FromHanyuPinyin(const std::string& str) {
pinyin = pinyin.substr(1); pinyin = pinyin.substr(1);
break; break;
case 'y': case 'y':
if (!secondComponent && !thirdComponent) { if (!secondComponent && !thirdComponent)
{
secondComponent = BPMF::I; secondComponent = BPMF::I;
} }
pinyin = pinyin.substr(1); pinyin = pinyin.substr(1);
@ -170,176 +202,283 @@ const BPMF BPMF::FromHanyuPinyin(const std::string& str) {
} }
// then we try ZH, CH, SH, R, Z, C, S (in that order) // then we try ZH, CH, SH, R, Z, C, S (in that order)
if (0) { if (0)
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "zh")) { {
}
else if (PinyinParseHelper::ConsumePrefix(pinyin, "zh"))
{
firstComponent = BPMF::ZH; firstComponent = BPMF::ZH;
independentConsonant = true; independentConsonant = true;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "ch")) { }
else if (PinyinParseHelper::ConsumePrefix(pinyin, "ch"))
{
firstComponent = BPMF::CH; firstComponent = BPMF::CH;
independentConsonant = true; independentConsonant = true;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "sh")) { }
else if (PinyinParseHelper::ConsumePrefix(pinyin, "sh"))
{
firstComponent = BPMF::SH; firstComponent = BPMF::SH;
independentConsonant = true; independentConsonant = true;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "r")) { }
else if (PinyinParseHelper::ConsumePrefix(pinyin, "r"))
{
firstComponent = BPMF::R; firstComponent = BPMF::R;
independentConsonant = true; independentConsonant = true;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "z")) { }
else if (PinyinParseHelper::ConsumePrefix(pinyin, "z"))
{
firstComponent = BPMF::Z; firstComponent = BPMF::Z;
independentConsonant = true; independentConsonant = true;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "c")) { }
else if (PinyinParseHelper::ConsumePrefix(pinyin, "c"))
{
firstComponent = BPMF::C; firstComponent = BPMF::C;
independentConsonant = true; independentConsonant = true;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "s")) { }
else if (PinyinParseHelper::ConsumePrefix(pinyin, "s"))
{
firstComponent = BPMF::S; firstComponent = BPMF::S;
independentConsonant = true; independentConsonant = true;
} }
// consume exceptions first: (ien, in), (iou, iu), (uen, un), (veng, iong), // consume exceptions first: (ien, in), (iou, iu), (uen, un), (veng, iong),
// (ven, vn), (uei, ui), ung but longer sequence takes precedence // (ven, vn), (uei, ui), ung but longer sequence takes precedence
if (0) { if (0)
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "veng")) { {
}
else if (PinyinParseHelper::ConsumePrefix(pinyin, "veng"))
{
secondComponent = BPMF::UE; secondComponent = BPMF::UE;
thirdComponent = BPMF::ENG; thirdComponent = BPMF::ENG;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "iong")) { }
else if (PinyinParseHelper::ConsumePrefix(pinyin, "iong"))
{
secondComponent = BPMF::UE; secondComponent = BPMF::UE;
thirdComponent = BPMF::ENG; thirdComponent = BPMF::ENG;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "ing")) { }
else if (PinyinParseHelper::ConsumePrefix(pinyin, "ing"))
{
secondComponent = BPMF::I; secondComponent = BPMF::I;
thirdComponent = BPMF::ENG; thirdComponent = BPMF::ENG;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "ien")) { }
else if (PinyinParseHelper::ConsumePrefix(pinyin, "ien"))
{
secondComponent = BPMF::I; secondComponent = BPMF::I;
thirdComponent = BPMF::EN; thirdComponent = BPMF::EN;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "iou")) { }
else if (PinyinParseHelper::ConsumePrefix(pinyin, "iou"))
{
secondComponent = BPMF::I; secondComponent = BPMF::I;
thirdComponent = BPMF::OU; thirdComponent = BPMF::OU;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "uen")) { }
else if (PinyinParseHelper::ConsumePrefix(pinyin, "uen"))
{
secondComponent = BPMF::U; secondComponent = BPMF::U;
thirdComponent = BPMF::EN; thirdComponent = BPMF::EN;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "ven")) { }
else if (PinyinParseHelper::ConsumePrefix(pinyin, "ven"))
{
secondComponent = BPMF::UE; secondComponent = BPMF::UE;
thirdComponent = BPMF::EN; thirdComponent = BPMF::EN;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "uei")) { }
else if (PinyinParseHelper::ConsumePrefix(pinyin, "uei"))
{
secondComponent = BPMF::U; secondComponent = BPMF::U;
thirdComponent = BPMF::EI; thirdComponent = BPMF::EI;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "ung")) { }
else if (PinyinParseHelper::ConsumePrefix(pinyin, "ung"))
{
// f exception // f exception
if (firstComponent == BPMF::F) { if (firstComponent == BPMF::F)
{
thirdComponent = BPMF::ENG; thirdComponent = BPMF::ENG;
} else { }
else
{
secondComponent = BPMF::U; secondComponent = BPMF::U;
thirdComponent = BPMF::ENG; thirdComponent = BPMF::ENG;
} }
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "ong")) { }
else if (PinyinParseHelper::ConsumePrefix(pinyin, "ong"))
{
// f exception // f exception
if (firstComponent == BPMF::F) { if (firstComponent == BPMF::F)
{
thirdComponent = BPMF::ENG; thirdComponent = BPMF::ENG;
} else { }
else
{
secondComponent = BPMF::U; secondComponent = BPMF::U;
thirdComponent = BPMF::ENG; thirdComponent = BPMF::ENG;
} }
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "un")) { }
if (firstComponent == BPMF::J || firstComponent == BPMF::Q || else if (PinyinParseHelper::ConsumePrefix(pinyin, "un"))
firstComponent == BPMF::X) { {
if (firstComponent == BPMF::J || firstComponent == BPMF::Q || firstComponent == BPMF::X)
{
secondComponent = BPMF::UE; secondComponent = BPMF::UE;
} else { }
else
{
secondComponent = BPMF::U; secondComponent = BPMF::U;
} }
thirdComponent = BPMF::EN; thirdComponent = BPMF::EN;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "iu")) { }
else if (PinyinParseHelper::ConsumePrefix(pinyin, "iu"))
{
secondComponent = BPMF::I; secondComponent = BPMF::I;
thirdComponent = BPMF::OU; thirdComponent = BPMF::OU;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "in")) { }
else if (PinyinParseHelper::ConsumePrefix(pinyin, "in"))
{
secondComponent = BPMF::I; secondComponent = BPMF::I;
thirdComponent = BPMF::EN; thirdComponent = BPMF::EN;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "vn")) { }
else if (PinyinParseHelper::ConsumePrefix(pinyin, "vn"))
{
secondComponent = BPMF::UE; secondComponent = BPMF::UE;
thirdComponent = BPMF::EN; thirdComponent = BPMF::EN;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "ui")) { }
else if (PinyinParseHelper::ConsumePrefix(pinyin, "ui"))
{
secondComponent = BPMF::U; secondComponent = BPMF::U;
thirdComponent = BPMF::EI; thirdComponent = BPMF::EI;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "ue")) { }
else if (PinyinParseHelper::ConsumePrefix(pinyin, "ue"))
{
secondComponent = BPMF::UE; secondComponent = BPMF::UE;
thirdComponent = BPMF::E; thirdComponent = BPMF::E;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, u8"ü")) { }
else if (PinyinParseHelper::ConsumePrefix(pinyin, u8"ü"))
{
secondComponent = BPMF::UE; secondComponent = BPMF::UE;
} }
// then consume the middle component... // then consume the middle component...
if (0) { if (0)
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "i")) { {
}
else if (PinyinParseHelper::ConsumePrefix(pinyin, "i"))
{
secondComponent = independentConsonant ? 0 : BPMF::I; secondComponent = independentConsonant ? 0 : BPMF::I;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "u")) { }
if (firstComponent == BPMF::J || firstComponent == BPMF::Q || else if (PinyinParseHelper::ConsumePrefix(pinyin, "u"))
firstComponent == BPMF::X) { {
if (firstComponent == BPMF::J || firstComponent == BPMF::Q || firstComponent == BPMF::X)
{
secondComponent = BPMF::UE; secondComponent = BPMF::UE;
} else { }
else
{
secondComponent = BPMF::U; secondComponent = BPMF::U;
} }
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "v")) { }
else if (PinyinParseHelper::ConsumePrefix(pinyin, "v"))
{
secondComponent = BPMF::UE; secondComponent = BPMF::UE;
} }
// the vowels, longer sequence takes precedence // the vowels, longer sequence takes precedence
if (0) { if (0)
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "ang")) { {
}
else if (PinyinParseHelper::ConsumePrefix(pinyin, "ang"))
{
thirdComponent = BPMF::ANG; thirdComponent = BPMF::ANG;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "eng")) { }
else if (PinyinParseHelper::ConsumePrefix(pinyin, "eng"))
{
thirdComponent = BPMF::ENG; thirdComponent = BPMF::ENG;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "err")) { }
else if (PinyinParseHelper::ConsumePrefix(pinyin, "err"))
{
thirdComponent = BPMF::ERR; thirdComponent = BPMF::ERR;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "ai")) { }
else if (PinyinParseHelper::ConsumePrefix(pinyin, "ai"))
{
thirdComponent = BPMF::AI; thirdComponent = BPMF::AI;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "ei")) { }
else if (PinyinParseHelper::ConsumePrefix(pinyin, "ei"))
{
thirdComponent = BPMF::EI; thirdComponent = BPMF::EI;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "ao")) { }
else if (PinyinParseHelper::ConsumePrefix(pinyin, "ao"))
{
thirdComponent = BPMF::AO; thirdComponent = BPMF::AO;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "ou")) { }
else if (PinyinParseHelper::ConsumePrefix(pinyin, "ou"))
{
thirdComponent = BPMF::OU; thirdComponent = BPMF::OU;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "an")) { }
else if (PinyinParseHelper::ConsumePrefix(pinyin, "an"))
{
thirdComponent = BPMF::AN; thirdComponent = BPMF::AN;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "en")) { }
else if (PinyinParseHelper::ConsumePrefix(pinyin, "en"))
{
thirdComponent = BPMF::EN; thirdComponent = BPMF::EN;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "er")) { }
else if (PinyinParseHelper::ConsumePrefix(pinyin, "er"))
{
thirdComponent = BPMF::ERR; thirdComponent = BPMF::ERR;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "a")) { }
else if (PinyinParseHelper::ConsumePrefix(pinyin, "a"))
{
thirdComponent = BPMF::A; thirdComponent = BPMF::A;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "o")) { }
else if (PinyinParseHelper::ConsumePrefix(pinyin, "o"))
{
thirdComponent = BPMF::O; thirdComponent = BPMF::O;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "e")) { }
if (secondComponent) { else if (PinyinParseHelper::ConsumePrefix(pinyin, "e"))
{
if (secondComponent)
{
thirdComponent = BPMF::E; thirdComponent = BPMF::E;
} else { }
else
{
thirdComponent = BPMF::ER; thirdComponent = BPMF::ER;
} }
} }
// at last! // at last!
if (0) { if (0)
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "1")) { {
}
else if (PinyinParseHelper::ConsumePrefix(pinyin, "1"))
{
toneComponent = BPMF::Tone1; toneComponent = BPMF::Tone1;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "2")) { }
else if (PinyinParseHelper::ConsumePrefix(pinyin, "2"))
{
toneComponent = BPMF::Tone2; toneComponent = BPMF::Tone2;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "3")) { }
else if (PinyinParseHelper::ConsumePrefix(pinyin, "3"))
{
toneComponent = BPMF::Tone3; toneComponent = BPMF::Tone3;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "4")) { }
else if (PinyinParseHelper::ConsumePrefix(pinyin, "4"))
{
toneComponent = BPMF::Tone4; toneComponent = BPMF::Tone4;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "5")) { }
else if (PinyinParseHelper::ConsumePrefix(pinyin, "5"))
{
toneComponent = BPMF::Tone5; toneComponent = BPMF::Tone5;
} }
return BPMF(firstComponent | secondComponent | thirdComponent | return BPMF(firstComponent | secondComponent | thirdComponent | toneComponent);
toneComponent);
} }
const std::string BPMF::HanyuPinyinString(bool includesTone, const std::string BPMF::HanyuPinyinString(bool includesTone, bool useVForUUmlaut) const
bool useVForUUmlaut) const { {
std::string consonant, middle, vowel, tone; std::string consonant, middle, vowel, tone;
Component cc = consonantComponent(), mvc = middleVowelComponent(), Component cc = consonantComponent(), mvc = middleVowelComponent(), vc = vowelComponent();
vc = vowelComponent();
bool hasNoMVCOrVC = !(mvc || vc); bool hasNoMVCOrVC = !(mvc || vc);
switch (cc) { switch (cc)
{
case B: case B:
consonant = "b"; consonant = "b";
break; break;
@ -375,75 +514,93 @@ const std::string BPMF::HanyuPinyinString(bool includesTone,
break; break;
case J: case J:
consonant = "j"; consonant = "j";
if (hasNoMVCOrVC) middle = "i"; if (hasNoMVCOrVC)
middle = "i";
break; break;
case Q: case Q:
consonant = "q"; consonant = "q";
if (hasNoMVCOrVC) middle = "i"; if (hasNoMVCOrVC)
middle = "i";
break; break;
case X: case X:
consonant = "x"; consonant = "x";
if (hasNoMVCOrVC) middle = "i"; if (hasNoMVCOrVC)
middle = "i";
break; break;
case ZH: case ZH:
consonant = "zh"; consonant = "zh";
if (hasNoMVCOrVC) middle = "i"; if (hasNoMVCOrVC)
middle = "i";
break; break;
case CH: case CH:
consonant = "ch"; consonant = "ch";
if (hasNoMVCOrVC) middle = "i"; if (hasNoMVCOrVC)
middle = "i";
break; break;
case SH: case SH:
consonant = "sh"; consonant = "sh";
if (hasNoMVCOrVC) middle = "i"; if (hasNoMVCOrVC)
middle = "i";
break; break;
case R: case R:
consonant = "r"; consonant = "r";
if (hasNoMVCOrVC) middle = "i"; if (hasNoMVCOrVC)
middle = "i";
break; break;
case Z: case Z:
consonant = "z"; consonant = "z";
if (hasNoMVCOrVC) middle = "i"; if (hasNoMVCOrVC)
middle = "i";
break; break;
case C: case C:
consonant = "c"; consonant = "c";
if (hasNoMVCOrVC) middle = "i"; if (hasNoMVCOrVC)
middle = "i";
break; break;
case S: case S:
consonant = "s"; consonant = "s";
if (hasNoMVCOrVC) middle = "i"; if (hasNoMVCOrVC)
middle = "i";
break; break;
} }
switch (mvc) { switch (mvc)
{
case I: case I:
if (!cc) { if (!cc)
{
consonant = "y"; consonant = "y";
} }
middle = (!vc || cc) ? "i" : ""; middle = (!vc || cc) ? "i" : "";
break; break;
case U: case U:
if (!cc) { if (!cc)
{
consonant = "w"; consonant = "w";
} }
middle = (!vc || cc) ? "u" : ""; middle = (!vc || cc) ? "u" : "";
break; break;
case UE: case UE:
if (!cc) { if (!cc)
{
consonant = "y"; consonant = "y";
} }
if ((cc == N || cc == L) && vc != E) { if ((cc == N || cc == L) && vc != E)
{
middle = useVForUUmlaut ? "v" : "ü"; middle = useVForUUmlaut ? "v" : "ü";
} else { }
else
{
middle = "u"; middle = "u";
} }
break; break;
} }
switch (vc) { switch (vc)
{
case A: case A:
vowel = "a"; vowel = "a";
break; break;
@ -488,48 +645,61 @@ const std::string BPMF::HanyuPinyinString(bool includesTone,
// combination rules // combination rules
// ueng -> ong, but note "weng" // ueng -> ong, but note "weng"
if ((mvc == U || mvc == UE) && vc == ENG) { if ((mvc == U || mvc == UE) && vc == ENG)
{
middle = ""; middle = "";
vowel = (cc == J || cc == Q || cc == X) vowel = (cc == J || cc == Q || cc == X) ? "iong" : ((!cc && mvc == U) ? "eng" : "ong");
? "iong"
: ((!cc && mvc == U) ? "eng" : "ong");
} }
// ien, uen, üen -> in, un, ün ; but note "wen", "yin" and "yun" // ien, uen, üen -> in, un, ün ; but note "wen", "yin" and "yun"
if (mvc && vc == EN) { if (mvc && vc == EN)
if (cc) { {
if (cc)
{
vowel = "n"; vowel = "n";
} else { }
if (mvc == UE) { else
{
if (mvc == UE)
{
vowel = "n"; // yun vowel = "n"; // yun
} else if (mvc == U) { }
else if (mvc == U)
{
vowel = "en"; // wen vowel = "en"; // wen
} else { }
else
{
vowel = "in"; // yin vowel = "in"; // yin
} }
} }
} }
// iou -> iu // iou -> iu
if (cc && mvc == I && vc == OU) { if (cc && mvc == I && vc == OU)
{
middle = ""; middle = "";
vowel = "iu"; vowel = "iu";
} }
// ieng -> ing // ieng -> ing
if (mvc == I && vc == ENG) { if (mvc == I && vc == ENG)
{
middle = ""; middle = "";
vowel = "ing"; vowel = "ing";
} }
// uei -> ui // uei -> ui
if (cc && mvc == U && vc == EI) { if (cc && mvc == U && vc == EI)
{
middle = ""; middle = "";
vowel = "ui"; vowel = "ui";
} }
if (includesTone) { if (includesTone)
switch (toneMarkerComponent()) { {
switch (toneMarkerComponent())
{
case Tone2: case Tone2:
tone = "2"; tone = "2";
break; break;
@ -548,44 +718,55 @@ const std::string BPMF::HanyuPinyinString(bool includesTone,
return consonant + middle + vowel + tone; return consonant + middle + vowel + tone;
} }
const BPMF BPMF::FromComposedString(const std::string& str) { const BPMF BPMF::FromComposedString(const std::string &str)
{
BPMF syllable; BPMF syllable;
auto iter = str.begin(); auto iter = str.begin();
while (iter != str.end()) { while (iter != str.end())
{
// This is a naive implementation and we bail early at anything we don't // This is a naive implementation and we bail early at anything we don't
// recognize. A sound implementation would require to either use a trie for // recognize. A sound implementation would require to either use a trie for
// the Bopomofo character map or to split the input by codepoints. This // the Bopomofo character map or to split the input by codepoints. This
// suffices for now. // suffices for now.
// Illegal. // Illegal.
if (!(*iter & 0x80)) { if (!(*iter & 0x80))
{
break; break;
} }
size_t utf8_length = -1; size_t utf8_length = -1;
// These are the code points for the tone markers. // These are the code points for the tone markers.
if ((*iter & (0x80 | 0x40)) && !(*iter & 0x20)) { if ((*iter & (0x80 | 0x40)) && !(*iter & 0x20))
{
utf8_length = 2; utf8_length = 2;
} else if ((*iter & (0x80 | 0x40 | 0x20)) && !(*iter & 0x10)) { }
else if ((*iter & (0x80 | 0x40 | 0x20)) && !(*iter & 0x10))
{
utf8_length = 3; utf8_length = 3;
} else { }
else
{
// Illegal. // Illegal.
break; break;
} }
if (iter + (utf8_length - 1) == str.end()) { if (iter + (utf8_length - 1) == str.end())
{
break; break;
} }
std::string component = std::string(iter, iter + utf8_length); std::string component = std::string(iter, iter + utf8_length);
const std::map<std::string, BPMF::Component> &charToComp = const std::map<std::string, BPMF::Component> &charToComp =
BopomofoCharacterMap::SharedInstance().characterToComponent; BopomofoCharacterMap::SharedInstance().characterToComponent;
std::map<std::string, BPMF::Component>::const_iterator result = std::map<std::string, BPMF::Component>::const_iterator result = charToComp.find(component);
charToComp.find(component); if (result == charToComp.end())
if (result == charToComp.end()) { {
break; break;
} else { }
else
{
syllable += BPMF((*result).second); syllable += BPMF((*result).second);
} }
iter += utf8_length; iter += utf8_length;
@ -593,14 +774,12 @@ const BPMF BPMF::FromComposedString(const std::string& str) {
return syllable; return syllable;
} }
const std::string BPMF::composedString() const { const std::string BPMF::composedString() const
{
std::string result; std::string result;
#define APPEND(c) \ #define APPEND(c) \
if (syllable_ & c) \ if (syllable_ & c) \
result += \ result += (*BopomofoCharacterMap::SharedInstance().componentToCharacter.find(syllable_ & c)).second
(*BopomofoCharacterMap::SharedInstance().componentToCharacter.find( \
syllable_ & c)) \
.second
APPEND(ConsonantMask); APPEND(ConsonantMask);
APPEND(MiddleVowelMask); APPEND(MiddleVowelMask);
APPEND(VowelMask); APPEND(VowelMask);
@ -609,14 +788,14 @@ syllable_ & c)) \
return result; return result;
} }
const BopomofoCharacterMap &BopomofoCharacterMap::SharedInstance()
{
const BopomofoCharacterMap& BopomofoCharacterMap::SharedInstance() {
static BopomofoCharacterMap *map = new BopomofoCharacterMap(); static BopomofoCharacterMap *map = new BopomofoCharacterMap();
return *map; return *map;
} }
BopomofoCharacterMap::BopomofoCharacterMap() { BopomofoCharacterMap::BopomofoCharacterMap()
{
characterToComponent[u8""] = BPMF::B; characterToComponent[u8""] = BPMF::B;
characterToComponent[u8""] = BPMF::P; characterToComponent[u8""] = BPMF::P;
characterToComponent[u8""] = BPMF::M; characterToComponent[u8""] = BPMF::M;
@ -659,23 +838,20 @@ BopomofoCharacterMap::BopomofoCharacterMap() {
characterToComponent[u8"ˋ"] = BPMF::Tone4; characterToComponent[u8"ˋ"] = BPMF::Tone4;
characterToComponent[u8"˙"] = BPMF::Tone5; characterToComponent[u8"˙"] = BPMF::Tone5;
for (std::map<std::string, BPMF::Component>::iterator iter = for (std::map<std::string, BPMF::Component>::iterator iter = characterToComponent.begin();
characterToComponent.begin();
iter != characterToComponent.end(); ++iter) iter != characterToComponent.end(); ++iter)
componentToCharacter[(*iter).second] = (*iter).first; componentToCharacter[(*iter).second] = (*iter).first;
} }
#define ASSIGNKEY1(m, vec, k, val) \ #define ASSIGNKEY1(m, vec, k, val) m[k] = (vec.clear(), vec.push_back((BPMF::Component)val), vec)
m[k] = (vec.clear(), vec.push_back((BPMF::Component)val), vec)
#define ASSIGNKEY2(m, vec, k, val1, val2) \ #define ASSIGNKEY2(m, vec, k, val1, val2) \
m[k] = (vec.clear(), vec.push_back((BPMF::Component)val1), \ m[k] = (vec.clear(), vec.push_back((BPMF::Component)val1), vec.push_back((BPMF::Component)val2), vec)
vec.push_back((BPMF::Component)val2), vec)
#define ASSIGNKEY3(m, vec, k, val1, val2, val3) \ #define ASSIGNKEY3(m, vec, k, val1, val2, val3) \
m[k] = (vec.clear(), vec.push_back((BPMF::Component)val1), \ m[k] = (vec.clear(), vec.push_back((BPMF::Component)val1), vec.push_back((BPMF::Component)val2), \
vec.push_back((BPMF::Component)val2), \
vec.push_back((BPMF::Component)val3), vec) vec.push_back((BPMF::Component)val3), vec)
static BopomofoKeyboardLayout* CreateStandardLayout() { static BopomofoKeyboardLayout *CreateStandardLayout()
{
std::vector<BPMF::Component> vec; std::vector<BPMF::Component> vec;
BopomofoKeyToComponentMap ktcm; BopomofoKeyToComponentMap ktcm;
@ -724,7 +900,8 @@ static BopomofoKeyboardLayout* CreateStandardLayout() {
return new BopomofoKeyboardLayout(ktcm, "Standard"); return new BopomofoKeyboardLayout(ktcm, "Standard");
} }
static BopomofoKeyboardLayout* CreateIBMLayout() { static BopomofoKeyboardLayout *CreateIBMLayout()
{
std::vector<BPMF::Component> vec; std::vector<BPMF::Component> vec;
BopomofoKeyToComponentMap ktcm; BopomofoKeyToComponentMap ktcm;
@ -773,7 +950,8 @@ static BopomofoKeyboardLayout* CreateIBMLayout() {
return new BopomofoKeyboardLayout(ktcm, "IBM"); return new BopomofoKeyboardLayout(ktcm, "IBM");
} }
static BopomofoKeyboardLayout* CreateMiTACLayout() { static BopomofoKeyboardLayout *CreateMiTACLayout()
{
std::vector<BPMF::Component> vec; std::vector<BPMF::Component> vec;
BopomofoKeyToComponentMap ktcm; BopomofoKeyToComponentMap ktcm;
@ -822,7 +1000,8 @@ static BopomofoKeyboardLayout* CreateMiTACLayout() {
return new BopomofoKeyboardLayout(ktcm, "MiTAC"); return new BopomofoKeyboardLayout(ktcm, "MiTAC");
} }
static BopomofoKeyboardLayout* CreateETenLayout() { static BopomofoKeyboardLayout *CreateETenLayout()
{
std::vector<BPMF::Component> vec; std::vector<BPMF::Component> vec;
BopomofoKeyToComponentMap ktcm; BopomofoKeyToComponentMap ktcm;
@ -871,7 +1050,8 @@ static BopomofoKeyboardLayout* CreateETenLayout() {
return new BopomofoKeyboardLayout(ktcm, "ETen"); return new BopomofoKeyboardLayout(ktcm, "ETen");
} }
static BopomofoKeyboardLayout* CreateHsuLayout() { static BopomofoKeyboardLayout *CreateHsuLayout()
{
std::vector<BPMF::Component> vec; std::vector<BPMF::Component> vec;
BopomofoKeyToComponentMap ktcm; BopomofoKeyToComponentMap ktcm;
@ -904,7 +1084,8 @@ static BopomofoKeyboardLayout* CreateHsuLayout() {
return new BopomofoKeyboardLayout(ktcm, "Hsu"); return new BopomofoKeyboardLayout(ktcm, "Hsu");
} }
static BopomofoKeyboardLayout* CreateETen26Layout() { static BopomofoKeyboardLayout *CreateETen26Layout()
{
std::vector<BPMF::Component> vec; std::vector<BPMF::Component> vec;
BopomofoKeyToComponentMap ktcm; BopomofoKeyToComponentMap ktcm;
@ -938,7 +1119,8 @@ static BopomofoKeyboardLayout* CreateETen26Layout() {
return new BopomofoKeyboardLayout(ktcm, "ETen26"); return new BopomofoKeyboardLayout(ktcm, "ETen26");
} }
static BopomofoKeyboardLayout* CreateFakeSeigyouLayout() { static BopomofoKeyboardLayout *CreateFakeSeigyouLayout()
{
std::vector<BPMF::Component> vec; std::vector<BPMF::Component> vec;
BopomofoKeyToComponentMap ktcm; BopomofoKeyToComponentMap ktcm;
@ -987,51 +1169,58 @@ static BopomofoKeyboardLayout* CreateFakeSeigyouLayout() {
return new BopomofoKeyboardLayout(ktcm, "FakeSeigyou"); return new BopomofoKeyboardLayout(ktcm, "FakeSeigyou");
} }
static BopomofoKeyboardLayout* CreateHanyuPinyinLayout() { static BopomofoKeyboardLayout *CreateHanyuPinyinLayout()
{
BopomofoKeyToComponentMap ktcm; BopomofoKeyToComponentMap ktcm;
return new BopomofoKeyboardLayout(ktcm, "HanyuPinyin"); return new BopomofoKeyboardLayout(ktcm, "HanyuPinyin");
} }
const BopomofoKeyboardLayout* BopomofoKeyboardLayout::StandardLayout() { const BopomofoKeyboardLayout *BopomofoKeyboardLayout::StandardLayout()
{
static BopomofoKeyboardLayout *layout = CreateStandardLayout(); static BopomofoKeyboardLayout *layout = CreateStandardLayout();
return layout; return layout;
} }
const BopomofoKeyboardLayout* BopomofoKeyboardLayout::ETenLayout() { const BopomofoKeyboardLayout *BopomofoKeyboardLayout::ETenLayout()
{
static BopomofoKeyboardLayout *layout = CreateETenLayout(); static BopomofoKeyboardLayout *layout = CreateETenLayout();
return layout; return layout;
} }
const BopomofoKeyboardLayout* BopomofoKeyboardLayout::HsuLayout() { const BopomofoKeyboardLayout *BopomofoKeyboardLayout::HsuLayout()
{
static BopomofoKeyboardLayout *layout = CreateHsuLayout(); static BopomofoKeyboardLayout *layout = CreateHsuLayout();
return layout; return layout;
} }
const BopomofoKeyboardLayout* BopomofoKeyboardLayout::ETen26Layout() { const BopomofoKeyboardLayout *BopomofoKeyboardLayout::ETen26Layout()
{
static BopomofoKeyboardLayout *layout = CreateETen26Layout(); static BopomofoKeyboardLayout *layout = CreateETen26Layout();
return layout; return layout;
} }
const BopomofoKeyboardLayout* BopomofoKeyboardLayout::IBMLayout() { const BopomofoKeyboardLayout *BopomofoKeyboardLayout::IBMLayout()
{
static BopomofoKeyboardLayout *layout = CreateIBMLayout(); static BopomofoKeyboardLayout *layout = CreateIBMLayout();
return layout; return layout;
} }
const BopomofoKeyboardLayout* BopomofoKeyboardLayout::MiTACLayout() { const BopomofoKeyboardLayout *BopomofoKeyboardLayout::MiTACLayout()
{
static BopomofoKeyboardLayout *layout = CreateMiTACLayout(); static BopomofoKeyboardLayout *layout = CreateMiTACLayout();
return layout; return layout;
} }
const BopomofoKeyboardLayout* BopomofoKeyboardLayout::FakeSeigyouLayout() { const BopomofoKeyboardLayout *BopomofoKeyboardLayout::FakeSeigyouLayout()
{
static BopomofoKeyboardLayout *layout = CreateFakeSeigyouLayout(); static BopomofoKeyboardLayout *layout = CreateFakeSeigyouLayout();
return layout; return layout;
} }
const BopomofoKeyboardLayout* BopomofoKeyboardLayout::HanyuPinyinLayout() { const BopomofoKeyboardLayout *BopomofoKeyboardLayout::HanyuPinyinLayout()
{
static BopomofoKeyboardLayout *layout = CreateHanyuPinyinLayout(); static BopomofoKeyboardLayout *layout = CreateHanyuPinyinLayout();
return layout; return layout;
} }
} // namespace Mandarin } // namespace Mandarin

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,13 +32,17 @@ 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(const BopomofoSyllable &) = default;
BopomofoSyllable(BopomofoSyllable &&another) = default; BopomofoSyllable(BopomofoSyllable &&another) = default;
@ -43,61 +54,93 @@ public:
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()
{
syllable_ = 0;
}
bool isEmpty() const { return !syllable_; } bool isEmpty() const
{
return !syllable_;
}
bool hasConsonant() const { return !!(syllable_ & ConsonantMask); } bool hasConsonant() const
{
return !!(syllable_ & ConsonantMask);
}
bool hasMiddleVowel() const { return !!(syllable_ & MiddleVowelMask); } bool hasMiddleVowel() const
bool hasVowel() const { return !!(syllable_ & VowelMask); } {
return !!(syllable_ & MiddleVowelMask);
}
bool hasVowel() const
{
return !!(syllable_ & VowelMask);
}
bool hasToneMarker() const { return !!(syllable_ & ToneMarkerMask); } bool hasToneMarker() const
{
return !!(syllable_ & ToneMarkerMask);
}
Component consonantComponent() const { return syllable_ & ConsonantMask; } Component consonantComponent() const
{
return syllable_ & ConsonantMask;
}
Component middleVowelComponent() const { Component middleVowelComponent() const
{
return syllable_ & MiddleVowelMask; return syllable_ & MiddleVowelMask;
} }
Component vowelComponent() const { return syllable_ & VowelMask; } Component vowelComponent() const
{
return syllable_ & VowelMask;
}
Component toneMarkerComponent() const { return syllable_ & ToneMarkerMask; } Component toneMarkerComponent() const
{
return syllable_ & ToneMarkerMask;
}
bool operator==(const BopomofoSyllable& another) const { 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;
@ -106,10 +149,12 @@ public:
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);
@ -120,9 +165,11 @@ newSyllable = (newSyllable & ~mask) | (another.syllable_ & mask); \
return BopomofoSyllable(newSyllable); return BopomofoSyllable(newSyllable);
} }
BopomofoSyllable& operator+=(const BopomofoSyllable& another) { BopomofoSyllable &operator+=(const BopomofoSyllable &another)
{
#define OPE_SOVER(mask) \ #define OPE_SOVER(mask) \
if (another.syllable_ & mask) { \ if (another.syllable_ & mask) \
{ \
syllable_ = (syllable_ & ~mask) | (another.syllable_ & mask); \ syllable_ = (syllable_ & ~mask) | (another.syllable_ & mask); \
} }
OPE_SOVER(ConsonantMask); OPE_SOVER(ConsonantMask);
@ -133,31 +180,27 @@ syllable_ = (syllable_ & ~mask) | (another.syllable_ & mask); \
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 static constexpr Component ConsonantMask = 0x001f, // 0000 0000 0001 1111, 21 consonants
ConsonantMask = 0x001f, // 0000 0000 0001 1111, 21 consonants
MiddleVowelMask = 0x0060, // 0000 0000 0110 0000, 3 middle vowels MiddleVowelMask = 0x0060, // 0000 0000 0110 0000, 3 middle vowels
VowelMask = 0x0780, // 0000 0111 1000 0000, 13 vowels VowelMask = 0x0780, // 0000 0111 1000 0000, 13 vowels
ToneMarkerMask = 0x3800, // 0011 1000 0000 0000, 5 tones (tone1 = 0x00) ToneMarkerMask = 0x3800, // 0011 1000 0000 0000, 5 tones (tone1 = 0x00)
B = 0x0001, P = 0x0002, M = 0x0003, F = 0x0004, D = 0x0005, T = 0x0006, B = 0x0001, P = 0x0002, M = 0x0003, F = 0x0004, D = 0x0005, T = 0x0006, N = 0x0007, L = 0x0008, G = 0x0009,
N = 0x0007, L = 0x0008, G = 0x0009, K = 0x000a, H = 0x000b, J = 0x000c, K = 0x000a, H = 0x000b, J = 0x000c, Q = 0x000d, X = 0x000e, ZH = 0x000f, CH = 0x0010,
Q = 0x000d, X = 0x000e, ZH = 0x000f, CH = 0x0010, SH = 0x0011, R = 0x0012, SH = 0x0011, R = 0x0012, Z = 0x0013, C = 0x0014, S = 0x0015, I = 0x0020, U = 0x0040,
Z = 0x0013, C = 0x0014, S = 0x0015, I = 0x0020, U = 0x0040,
UE = 0x0060, // ue = u umlaut (we use the German convention here as an UE = 0x0060, // ue = u umlaut (we use the German convention here as an
// ersatz to the /ju:/ sound) // ersatz to the /ju:/ sound)
A = 0x0080, O = 0x0100, ER = 0x0180, E = 0x0200, AI = 0x0280, EI = 0x0300, A = 0x0080, O = 0x0100, ER = 0x0180, E = 0x0200, AI = 0x0280, EI = 0x0300, AO = 0x0380, OU = 0x0400,
AO = 0x0380, OU = 0x0400, AN = 0x0480, EN = 0x0500, ANG = 0x0580, AN = 0x0480, EN = 0x0500, ANG = 0x0580, ENG = 0x0600, ERR = 0x0680, Tone1 = 0x0000,
ENG = 0x0600, ERR = 0x0680, Tone1 = 0x0000, Tone2 = 0x0800, Tone2 = 0x0800, Tone3 = 0x1000, Tone4 = 0x1800, Tone5 = 0x2000;
Tone3 = 0x1000, Tone4 = 0x1800, Tone5 = 0x2000;
protected: 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;
} }
@ -167,7 +210,8 @@ 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: public:
static const BopomofoKeyboardLayout *StandardLayout(); static const BopomofoKeyboardLayout *StandardLayout();
static const BopomofoKeyboardLayout *ETenLayout(); static const BopomofoKeyboardLayout *ETenLayout();
@ -178,40 +222,44 @@ public:
static const BopomofoKeyboardLayout *FakeSeigyouLayout(); static const BopomofoKeyboardLayout *FakeSeigyouLayout();
static const BopomofoKeyboardLayout *HanyuPinyinLayout(); static const BopomofoKeyboardLayout *HanyuPinyinLayout();
BopomofoKeyboardLayout(const BopomofoKeyToComponentMap& ktcm, BopomofoKeyboardLayout(const BopomofoKeyToComponentMap &ktcm, const std::string &name)
const std::string& name) : m_keyToComponent(ktcm), m_name(name)
: m_keyToComponent(ktcm), m_name(name) { {
for (BopomofoKeyToComponentMap::const_iterator miter = for (BopomofoKeyToComponentMap::const_iterator miter = m_keyToComponent.begin();
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
{
return m_name;
}
char componentToKey(BPMF::Component component) const { char componentToKey(BPMF::Component component) const
BopomofoComponentToKeyMap::const_iterator iter = {
m_componentToKey.find(component); 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());
@ -221,19 +269,22 @@ if ((k = componentToKey(c))) sequence += std::string(1, k); \
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;
} }
@ -243,34 +294,44 @@ if ((k = componentToKey(c))) sequence += std::string(1, k); \
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;
} }
@ -278,14 +339,20 @@ if ((k = componentToKey(c))) sequence += std::string(1, k); \
} }
// 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;
} }
} }
@ -293,30 +360,39 @@ if ((k = componentToKey(c))) sequence += std::string(1, k); \
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);
} }
} }
@ -325,9 +401,10 @@ if ((k = componentToKey(c))) sequence += std::string(1, k); \
} }
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);
@ -336,22 +413,23 @@ protected:
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 || if (*ahead == tone2 || *ahead == tone3 || *ahead == tone4 || *ahead == tone5)
*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;
} }
@ -360,36 +438,45 @@ protected:
BopomofoComponentToKeyMap m_componentToKey; BopomofoComponentToKeyMap m_componentToKey;
}; };
class BopomofoReadingBuffer { class BopomofoReadingBuffer
{
public: public:
explicit BopomofoReadingBuffer(const BopomofoKeyboardLayout* layout) explicit BopomofoReadingBuffer(const BopomofoKeyboardLayout *layout) : layout_(layout), pinyin_mode_(false)
: 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;
@ -397,40 +484,47 @@ public:
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))
return false;
if (pinyin_mode_) { 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_)
return;
if (pinyin_mode_) { if (pinyin_mode_)
if (pinyin_sequence_.length()) { {
pinyin_sequence_ = if (pinyin_sequence_.length())
pinyin_sequence_.substr(0, pinyin_sequence_.length() - 1); {
pinyin_sequence_ = pinyin_sequence_.substr(0, pinyin_sequence_.length() - 1);
} }
syllable_ = BPMF::FromHanyuPinyin(pinyin_sequence_); syllable_ = BPMF::FromHanyuPinyin(pinyin_sequence_);
@ -438,29 +532,42 @@ public:
} }
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
{
return syllable_.isEmpty();
}
const std::string composedString() const { const std::string composedString() const
if (pinyin_mode_) { {
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
{
return syllable_;
}
const std::string standardLayoutQueryString() const { const std::string standardLayoutQueryString() const
{
return BopomofoKeyboardLayout::StandardLayout()->keySequenceFromSyllable(syllable_); return BopomofoKeyboardLayout::StandardLayout()->keySequenceFromSyllable(syllable_);
} }
bool hasToneMarker() const { return syllable_.hasToneMarker(); } bool hasToneMarker() const
{
return syllable_.hasToneMarker();
}
protected: protected:
const BopomofoKeyboardLayout *layout_; const BopomofoKeyboardLayout *layout_;
@ -471,5 +578,4 @@ protected:
}; };
} // 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

@ -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
@ -49,37 +56,52 @@ enum VersionUpdateApiError: Error, LocalizedError {
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(
forced: Bool, callback: @escaping (Result<VersionUpdateApiResult, Error>) -> Void
) -> URLSessionTask? {
guard let infoDict = Bundle.main.infoDictionary, guard let infoDict = Bundle.main.infoDictionary,
let updateInfoURLString = infoDict[kUpdateInfoEndpointKey] as? String, let updateInfoURLString = infoDict[kUpdateInfoEndpointKey] as? String,
let updateInfoURL = URL(string: updateInfoURLString) else { let updateInfoURL = URL(string: updateInfoURLString)
else {
return nil return nil
} }
let request = URLRequest(url: updateInfoURL, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: kTimeoutInterval) let request = URLRequest(
url: updateInfoURL, cachePolicy: .reloadIgnoringLocalCacheData,
timeoutInterval: kTimeoutInterval)
let task = URLSession.shared.dataTask(with: request) { data, response, error in let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error { if let error = error {
DispatchQueue.main.async { DispatchQueue.main.async {
forced ? forced
callback(.failure(VersionUpdateApiError.connectionError(message: error.localizedDescription))) : ? callback(
callback(.success(.ignored)) .failure(
VersionUpdateApiError.connectionError(
message: error.localizedDescription)))
: callback(.success(.ignored))
} }
return return
} }
do { do {
guard let plist = try PropertyListSerialization.propertyList(from: data ?? Data(), options: [], format: nil) as? [AnyHashable: Any], guard
let plist = try PropertyListSerialization.propertyList(
from: data ?? Data(), options: [], format: nil) as? [AnyHashable: Any],
let remoteVersion = plist[kCFBundleVersionKey] as? String, let remoteVersion = plist[kCFBundleVersionKey] as? String,
let infoDict = Bundle.main.infoDictionary let infoDict = Bundle.main.infoDictionary
else { else {
DispatchQueue.main.async { DispatchQueue.main.async {
forced ? callback(.success(.noNeedToUpdate)) : callback(.success(.ignored)) forced
? callback(.success(.noNeedToUpdate))
: callback(.success(.ignored))
} }
return return
} }
@ -88,26 +110,36 @@ struct VersionUpdateApi {
// 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))
: callback(.success(.ignored))
} }
IME.prtDebugIntel("vChewingDebug: Update // Order is not Ascending, assuming that there's no new version available.") IME.prtDebugIntel(
"vChewingDebug: Update // Order is not Ascending, assuming that there's no new version available."
)
return return
} }
IME.prtDebugIntel("vChewingDebug: Update // New version detected, proceeding to the next phase.") IME.prtDebugIntel(
"vChewingDebug: Update // New version detected, proceeding to the next phase.")
guard let siteInfoURLString = plist[kUpdateInfoSiteKey] as? String, guard let siteInfoURLString = plist[kUpdateInfoSiteKey] as? String,
let siteInfoURL = URL(string: siteInfoURLString) let siteInfoURL = URL(string: siteInfoURLString)
else { else {
DispatchQueue.main.async { DispatchQueue.main.async {
forced ? callback(.success(.noNeedToUpdate)) : callback(.success(.ignored)) forced
? callback(.success(.noNeedToUpdate))
: callback(.success(.ignored))
} }
IME.prtDebugIntel("vChewingDebug: Update // Failed from retrieving / parsing URL intel.") IME.prtDebugIntel(
"vChewingDebug: Update // Failed from retrieving / parsing URL intel.")
return return
} }
IME.prtDebugIntel("vChewingDebug: Update // URL intel retrieved, proceeding to the next phase.") IME.prtDebugIntel(
"vChewingDebug: Update // URL intel retrieved, proceeding to the next phase.")
var report = VersionUpdateReport(siteUrl: siteInfoURL) var report = VersionUpdateReport(siteUrl: siteInfoURL)
var versionDescription = "" var versionDescription = ""
let versionDescriptions = plist[kVersionDescription] as? [AnyHashable: Any] let versionDescriptions = plist[kVersionDescription] as? [AnyHashable: Any]
@ -118,7 +150,9 @@ struct VersionUpdateApi {
if let first = preferredTags.first { if let first = preferredTags.first {
locale = first locale = first
} }
versionDescription = versionDescriptions[locale] as? String ?? versionDescriptions["en"] as? String ?? "" versionDescription =
versionDescriptions[locale] as? String ?? versionDescriptions["en"]
as? String ?? ""
if !versionDescription.isEmpty { if !versionDescription.isEmpty {
versionDescription = "\n\n" + versionDescription versionDescription = "\n\n" + versionDescription
} }
@ -144,7 +178,9 @@ struct VersionUpdateApi {
} }
@objc(AppDelegate) @objc(AppDelegate)
class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelegate, FSEventStreamHelperDelegate { class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelegate,
FSEventStreamHelperDelegate
{
func helper(_ helper: FSEventStreamHelper, didReceive events: [FSEventStreamHelper.Event]) { func helper(_ helper: FSEventStreamHelper, didReceive events: [FSEventStreamHelper.Event]) {
// 100ms 使使 // 100ms 使使
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) { DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) {
@ -161,7 +197,9 @@ class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelega
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(
path: mgrLangModel.dataFolderPath(isDefaultFolder: false),
queue: DispatchQueue(label: "vChewing User Phrases"))
private var currentAlertType: String = "" private var currentAlertType: String = ""
// dealloc // dealloc
@ -188,7 +226,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelega
} }
@objc func showPreferences() { @objc func showPreferences() {
if (ctlPrefWindowInstance == nil) { if ctlPrefWindowInstance == nil {
ctlPrefWindowInstance = ctlPrefWindow.init(windowNibName: "frmPrefWindow") ctlPrefWindowInstance = ctlPrefWindow.init(windowNibName: "frmPrefWindow")
} }
ctlPrefWindowInstance?.window?.center() ctlPrefWindowInstance?.window?.center()
@ -200,7 +238,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelega
// New About Window // New About Window
@objc func showAbout() { @objc func showAbout() {
if (ctlAboutWindowInstance == nil) { if ctlAboutWindowInstance == nil {
ctlAboutWindowInstance = ctlAboutWindow.init(windowNibName: "frmAboutWindow") ctlAboutWindowInstance = ctlAboutWindow.init(windowNibName: "frmAboutWindow")
} }
ctlAboutWindowInstance?.window?.center() ctlAboutWindowInstance?.window?.center()
@ -246,7 +284,10 @@ class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelega
switch apiResult { switch apiResult {
case .shouldUpdate(let report): case .shouldUpdate(let report):
self.updateNextStepURL = report.siteUrl self.updateNextStepURL = report.siteUrl
let content = String(format: NSLocalizedString("You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?%@", comment: ""), 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.currentShortVersion,
report.currentVersion, report.currentVersion,
report.remoteShortVersion, report.remoteShortVersion,
@ -254,7 +295,16 @@ class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelega
report.versionDescription) report.versionDescription)
IME.prtDebugIntel("vChewingDebug: \(content)") IME.prtDebugIntel("vChewingDebug: \(content)")
self.currentAlertType = "Update" 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) 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) NSApp.setActivationPolicy(.accessory)
case .noNeedToUpdate, .ignored: case .noNeedToUpdate, .ignored:
break break
@ -262,12 +312,20 @@ class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelega
case .failure(let error): case .failure(let error):
switch error { switch error {
case VersionUpdateApiError.connectionError(let message): case VersionUpdateApiError.connectionError(let message):
let title = NSLocalizedString("Update Check Failed", comment: "") let title = NSLocalizedString(
let content = String(format: NSLocalizedString("There may be no internet connection or the server failed to respond.\n\nError message: %@", comment: ""), message) "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: "") let buttonTitle = NSLocalizedString("Dismiss", comment: "")
IME.prtDebugIntel("vChewingDebug: \(content)") IME.prtDebugIntel("vChewingDebug: \(content)")
self.currentAlertType = "Update" self.currentAlertType = "Update"
ctlNonModalAlertWindow.shared.show(title: title, content: content, confirmButtonTitle: buttonTitle, cancelButtonTitle: nil, cancelAsDefault: false, delegate: nil) ctlNonModalAlertWindow.shared.show(
title: title, content: content,
confirmButtonTitle: buttonTitle,
cancelButtonTitle: nil,
cancelAsDefault: false, delegate: nil)
NSApp.setActivationPolicy(.accessory) NSApp.setActivationPolicy(.accessory)
default: default:
break break
@ -278,15 +336,23 @@ class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelega
func selfUninstall() { func selfUninstall() {
self.currentAlertType = "Uninstall" self.currentAlertType = "Uninstall"
let content = String(format: NSLocalizedString("This will remove vChewing Input Method from this user account, requiring your confirmation.", comment: "")) let content = String(
ctlNonModalAlertWindow.shared.show(title: NSLocalizedString("Uninstallation", comment: ""), content: content, confirmButtonTitle: NSLocalizedString("OK", comment: ""), cancelButtonTitle: NSLocalizedString("Not Now", comment: ""), cancelAsDefault: false, delegate: self) 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) NSApp.setActivationPolicy(.accessory)
} }
func ctlNonModalAlertWindowDidConfirm(_ controller: ctlNonModalAlertWindow) { func ctlNonModalAlertWindowDidConfirm(_ controller: ctlNonModalAlertWindow) {
switch self.currentAlertType { switch self.currentAlertType {
case "Uninstall": case "Uninstall":
NSWorkspace.shared.openFile(mgrLangModel.dataFolderPath(isDefaultFolder: true), withApplication: "Finder") NSWorkspace.shared.openFile(
mgrLangModel.dataFolderPath(isDefaultFolder: true), withApplication: "Finder")
IME.uninstall(isSudo: false, selfKill: true) IME.uninstall(isSudo: false, selfKill: true)
case "Update": case "Update":
if let updateNextStepURL = self.updateNextStepURL { if let updateNextStepURL = self.updateNextStepURL {

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
@ -56,134 +62,136 @@ import Cocoa
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 { case "com.apple.keylayout.ZhuyinEten":
if (charCode == 65345) {charCode = UniChar(65)} do {
if (charCode == 65346) {charCode = UniChar(66)} if charCode == 65345 { charCode = UniChar(65) }
if (charCode == 65347) {charCode = UniChar(67)} if charCode == 65346 { charCode = UniChar(66) }
if (charCode == 65348) {charCode = UniChar(68)} if charCode == 65347 { charCode = UniChar(67) }
if (charCode == 65349) {charCode = UniChar(69)} if charCode == 65348 { charCode = UniChar(68) }
if (charCode == 65350) {charCode = UniChar(70)} if charCode == 65349 { charCode = UniChar(69) }
if (charCode == 65351) {charCode = UniChar(71)} if charCode == 65350 { charCode = UniChar(70) }
if (charCode == 65352) {charCode = UniChar(72)} if charCode == 65351 { charCode = UniChar(71) }
if (charCode == 65353) {charCode = UniChar(73)} if charCode == 65352 { charCode = UniChar(72) }
if (charCode == 65354) {charCode = UniChar(74)} if charCode == 65353 { charCode = UniChar(73) }
if (charCode == 65355) {charCode = UniChar(75)} if charCode == 65354 { charCode = UniChar(74) }
if (charCode == 65356) {charCode = UniChar(76)} if charCode == 65355 { charCode = UniChar(75) }
if (charCode == 65357) {charCode = UniChar(77)} if charCode == 65356 { charCode = UniChar(76) }
if (charCode == 65358) {charCode = UniChar(78)} if charCode == 65357 { charCode = UniChar(77) }
if (charCode == 65359) {charCode = UniChar(79)} if charCode == 65358 { charCode = UniChar(78) }
if (charCode == 65360) {charCode = UniChar(80)} if charCode == 65359 { charCode = UniChar(79) }
if (charCode == 65361) {charCode = UniChar(81)} if charCode == 65360 { charCode = UniChar(80) }
if (charCode == 65362) {charCode = UniChar(82)} if charCode == 65361 { charCode = UniChar(81) }
if (charCode == 65363) {charCode = UniChar(83)} if charCode == 65362 { charCode = UniChar(82) }
if (charCode == 65364) {charCode = UniChar(84)} if charCode == 65363 { charCode = UniChar(83) }
if (charCode == 65365) {charCode = UniChar(85)} if charCode == 65364 { charCode = UniChar(84) }
if (charCode == 65366) {charCode = UniChar(86)} if charCode == 65365 { charCode = UniChar(85) }
if (charCode == 65367) {charCode = UniChar(87)} if charCode == 65366 { charCode = UniChar(86) }
if (charCode == 65368) {charCode = UniChar(88)} if charCode == 65367 { charCode = UniChar(87) }
if (charCode == 65369) {charCode = UniChar(89)} if charCode == 65368 { charCode = UniChar(88) }
if (charCode == 65370) {charCode = UniChar(90)} if charCode == 65369 { charCode = UniChar(89) }
if charCode == 65370 { charCode = UniChar(90) }
} }
default: break default: break
} }
// //
if (charCode == 12573) {charCode = UniChar(44)} if charCode == 12573 { charCode = UniChar(44) }
if (charCode == 12582) {charCode = UniChar(45)} if charCode == 12582 { charCode = UniChar(45) }
if (charCode == 12577) {charCode = UniChar(46)} if charCode == 12577 { charCode = UniChar(46) }
if (charCode == 12581) {charCode = UniChar(47)} if charCode == 12581 { charCode = UniChar(47) }
if (charCode == 12578) {charCode = UniChar(48)} if charCode == 12578 { charCode = UniChar(48) }
if (charCode == 12549) {charCode = UniChar(49)} if charCode == 12549 { charCode = UniChar(49) }
if (charCode == 12553) {charCode = UniChar(50)} if charCode == 12553 { charCode = UniChar(50) }
if (charCode == 711) {charCode = UniChar(51)} if charCode == 711 { charCode = UniChar(51) }
if (charCode == 715) {charCode = UniChar(52)} if charCode == 715 { charCode = UniChar(52) }
if (charCode == 12563) {charCode = UniChar(53)} if charCode == 12563 { charCode = UniChar(53) }
if (charCode == 714) {charCode = UniChar(54)} if charCode == 714 { charCode = UniChar(54) }
if (charCode == 729) {charCode = UniChar(55)} if charCode == 729 { charCode = UniChar(55) }
if (charCode == 12570) {charCode = UniChar(56)} if charCode == 12570 { charCode = UniChar(56) }
if (charCode == 12574) {charCode = UniChar(57)} if charCode == 12574 { charCode = UniChar(57) }
if (charCode == 12580) {charCode = UniChar(59)} if charCode == 12580 { charCode = UniChar(59) }
if (charCode == 12551) {charCode = UniChar(97)} if charCode == 12551 { charCode = UniChar(97) }
if (charCode == 12566) {charCode = UniChar(98)} if charCode == 12566 { charCode = UniChar(98) }
if (charCode == 12559) {charCode = UniChar(99)} if charCode == 12559 { charCode = UniChar(99) }
if (charCode == 12558) {charCode = UniChar(100)} if charCode == 12558 { charCode = UniChar(100) }
if (charCode == 12557) {charCode = UniChar(101)} if charCode == 12557 { charCode = UniChar(101) }
if (charCode == 12561) {charCode = UniChar(102)} if charCode == 12561 { charCode = UniChar(102) }
if (charCode == 12565) {charCode = UniChar(103)} if charCode == 12565 { charCode = UniChar(103) }
if (charCode == 12568) {charCode = UniChar(104)} if charCode == 12568 { charCode = UniChar(104) }
if (charCode == 12571) {charCode = UniChar(105)} if charCode == 12571 { charCode = UniChar(105) }
if (charCode == 12584) {charCode = UniChar(106)} if charCode == 12584 { charCode = UniChar(106) }
if (charCode == 12572) {charCode = UniChar(107)} if charCode == 12572 { charCode = UniChar(107) }
if (charCode == 12576) {charCode = UniChar(108)} if charCode == 12576 { charCode = UniChar(108) }
if (charCode == 12585) {charCode = UniChar(109)} if charCode == 12585 { charCode = UniChar(109) }
if (charCode == 12569) {charCode = UniChar(110)} if charCode == 12569 { charCode = UniChar(110) }
if (charCode == 12575) {charCode = UniChar(111)} if charCode == 12575 { charCode = UniChar(111) }
if (charCode == 12579) {charCode = UniChar(112)} if charCode == 12579 { charCode = UniChar(112) }
if (charCode == 12550) {charCode = UniChar(113)} if charCode == 12550 { charCode = UniChar(113) }
if (charCode == 12560) {charCode = UniChar(114)} if charCode == 12560 { charCode = UniChar(114) }
if (charCode == 12555) {charCode = UniChar(115)} if charCode == 12555 { charCode = UniChar(115) }
if (charCode == 12564) {charCode = UniChar(116)} if charCode == 12564 { charCode = UniChar(116) }
if (charCode == 12583) {charCode = UniChar(117)} if charCode == 12583 { charCode = UniChar(117) }
if (charCode == 12562) {charCode = UniChar(118)} if charCode == 12562 { charCode = UniChar(118) }
if (charCode == 12554) {charCode = UniChar(119)} if charCode == 12554 { charCode = UniChar(119) }
if (charCode == 12556) {charCode = UniChar(120)} if charCode == 12556 { charCode = UniChar(120) }
if (charCode == 12567) {charCode = UniChar(121)} if charCode == 12567 { charCode = UniChar(121) }
if (charCode == 12552) {charCode = UniChar(122)} if charCode == 12552 { charCode = UniChar(122) }
// //
if (charCode == 12289) {charCode = UniChar(92)} if charCode == 12289 { charCode = UniChar(92) }
if (charCode == 12300) {charCode = UniChar(91)} if charCode == 12300 { charCode = UniChar(91) }
if (charCode == 12301) {charCode = UniChar(93)} if charCode == 12301 { charCode = UniChar(93) }
if (charCode == 12302) {charCode = UniChar(123)} if charCode == 12302 { charCode = UniChar(123) }
if (charCode == 12303) {charCode = UniChar(125)} if charCode == 12303 { charCode = UniChar(125) }
if (charCode == 65292) {charCode = UniChar(60)} if charCode == 65292 { charCode = UniChar(60) }
if (charCode == 12290) {charCode = UniChar(62)} if charCode == 12290 { charCode = UniChar(62) }
// SHIFT // SHIFT
if (charCode == 65281) {charCode = UniChar(33)} if charCode == 65281 { charCode = UniChar(33) }
if (charCode == 65312) {charCode = UniChar(64)} if charCode == 65312 { charCode = UniChar(64) }
if (charCode == 65283) {charCode = UniChar(35)} if charCode == 65283 { charCode = UniChar(35) }
if (charCode == 65284) {charCode = UniChar(36)} if charCode == 65284 { charCode = UniChar(36) }
if (charCode == 65285) {charCode = UniChar(37)} if charCode == 65285 { charCode = UniChar(37) }
if (charCode == 65087) {charCode = UniChar(94)} if charCode == 65087 { charCode = UniChar(94) }
if (charCode == 65286) {charCode = UniChar(38)} if charCode == 65286 { charCode = UniChar(38) }
if (charCode == 65290) {charCode = UniChar(42)} if charCode == 65290 { charCode = UniChar(42) }
if (charCode == 65288) {charCode = UniChar(40)} if charCode == 65288 { charCode = UniChar(40) }
if (charCode == 65289) {charCode = UniChar(41)} if charCode == 65289 { charCode = UniChar(41) }
// Alt // Alt
if (charCode == 8212) {charCode = UniChar(45)} if charCode == 8212 { charCode = UniChar(45) }
// Apple // Apple
if mgrPrefs.basisKeyboardLayout == "com.apple.keylayout.ZhuyinEten" { if mgrPrefs.basisKeyboardLayout == "com.apple.keylayout.ZhuyinEten" {
if (charCode == 65343) {charCode = UniChar(95)} if charCode == 65343 { charCode = UniChar(95) }
if (charCode == 65306) {charCode = UniChar(58)} if charCode == 65306 { charCode = UniChar(58) }
if (charCode == 65311) {charCode = UniChar(63)} if charCode == 65311 { charCode = UniChar(63) }
if (charCode == 65291) {charCode = UniChar(43)} if charCode == 65291 { charCode = UniChar(43) }
if (charCode == 65372) {charCode = UniChar(124)} if charCode == 65372 { charCode = UniChar(124) }
} }
} }
return charCode return charCode
@ -194,134 +202,136 @@ import Cocoa
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 { case "com.apple.keylayout.ZhuyinEten":
if (strProcessed == "") {strProcessed = "A"} do {
if (strProcessed == "") {strProcessed = "B"} if strProcessed == "" { strProcessed = "A" }
if (strProcessed == "") {strProcessed = "C"} if strProcessed == "" { strProcessed = "B" }
if (strProcessed == "") {strProcessed = "D"} if strProcessed == "" { strProcessed = "C" }
if (strProcessed == "") {strProcessed = "E"} if strProcessed == "" { strProcessed = "D" }
if (strProcessed == "") {strProcessed = "F"} if strProcessed == "" { strProcessed = "E" }
if (strProcessed == "") {strProcessed = "G"} if strProcessed == "" { strProcessed = "F" }
if (strProcessed == "") {strProcessed = "H"} if strProcessed == "" { strProcessed = "G" }
if (strProcessed == "") {strProcessed = "I"} if strProcessed == "" { strProcessed = "H" }
if (strProcessed == "") {strProcessed = "J"} if strProcessed == "" { strProcessed = "I" }
if (strProcessed == "") {strProcessed = "K"} if strProcessed == "" { strProcessed = "J" }
if (strProcessed == "") {strProcessed = "L"} if strProcessed == "" { strProcessed = "K" }
if (strProcessed == "") {strProcessed = "M"} if strProcessed == "" { strProcessed = "L" }
if (strProcessed == "") {strProcessed = "N"} if strProcessed == "" { strProcessed = "M" }
if (strProcessed == "") {strProcessed = "O"} if strProcessed == "" { strProcessed = "N" }
if (strProcessed == "") {strProcessed = "P"} if strProcessed == "" { strProcessed = "O" }
if (strProcessed == "") {strProcessed = "Q"} if strProcessed == "" { strProcessed = "P" }
if (strProcessed == "") {strProcessed = "R"} if strProcessed == "" { strProcessed = "Q" }
if (strProcessed == "") {strProcessed = "S"} if strProcessed == "" { strProcessed = "R" }
if (strProcessed == "") {strProcessed = "T"} if strProcessed == "" { strProcessed = "S" }
if (strProcessed == "") {strProcessed = "U"} if strProcessed == "" { strProcessed = "T" }
if (strProcessed == "") {strProcessed = "V"} if strProcessed == "" { strProcessed = "U" }
if (strProcessed == "") {strProcessed = "W"} if strProcessed == "" { strProcessed = "V" }
if (strProcessed == "") {strProcessed = "X"} if strProcessed == "" { strProcessed = "W" }
if (strProcessed == "") {strProcessed = "Y"} if strProcessed == "" { strProcessed = "X" }
if (strProcessed == "") {strProcessed = "Z"} if strProcessed == "" { strProcessed = "Y" }
if strProcessed == "" { strProcessed = "Z" }
} }
default: break default: break
} }
// //
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 = "0"} if strProcessed == "" { strProcessed = "0" }
if (strProcessed == "") {strProcessed = "1"} if strProcessed == "" { strProcessed = "1" }
if (strProcessed == "") {strProcessed = "2"} if strProcessed == "" { strProcessed = "2" }
if (strProcessed == "ˇ") {strProcessed = "3"} if strProcessed == "ˇ" { strProcessed = "3" }
if (strProcessed == "ˋ") {strProcessed = "4"} if strProcessed == "ˋ" { strProcessed = "4" }
if (strProcessed == "") {strProcessed = "5"} if strProcessed == "" { strProcessed = "5" }
if (strProcessed == "ˊ") {strProcessed = "6"} if strProcessed == "ˊ" { strProcessed = "6" }
if (strProcessed == "˙") {strProcessed = "7"} if strProcessed == "˙" { strProcessed = "7" }
if (strProcessed == "") {strProcessed = "8"} if strProcessed == "" { strProcessed = "8" }
if (strProcessed == "") {strProcessed = "9"} if strProcessed == "" { strProcessed = "9" }
if (strProcessed == "") {strProcessed = ";"} if strProcessed == "" { strProcessed = ";" }
if (strProcessed == "") {strProcessed = "a"} if strProcessed == "" { strProcessed = "a" }
if (strProcessed == "") {strProcessed = "b"} if strProcessed == "" { strProcessed = "b" }
if (strProcessed == "") {strProcessed = "c"} if strProcessed == "" { strProcessed = "c" }
if (strProcessed == "") {strProcessed = "d"} if strProcessed == "" { strProcessed = "d" }
if (strProcessed == "") {strProcessed = "e"} if strProcessed == "" { strProcessed = "e" }
if (strProcessed == "") {strProcessed = "f"} if strProcessed == "" { strProcessed = "f" }
if (strProcessed == "") {strProcessed = "g"} if strProcessed == "" { strProcessed = "g" }
if (strProcessed == "") {strProcessed = "h"} if strProcessed == "" { strProcessed = "h" }
if (strProcessed == "") {strProcessed = "i"} if strProcessed == "" { strProcessed = "i" }
if (strProcessed == "") {strProcessed = "j"} if strProcessed == "" { strProcessed = "j" }
if (strProcessed == "") {strProcessed = "k"} if strProcessed == "" { strProcessed = "k" }
if (strProcessed == "") {strProcessed = "l"} if strProcessed == "" { strProcessed = "l" }
if (strProcessed == "") {strProcessed = "m"} if strProcessed == "" { strProcessed = "m" }
if (strProcessed == "") {strProcessed = "n"} if strProcessed == "" { strProcessed = "n" }
if (strProcessed == "") {strProcessed = "o"} if strProcessed == "" { strProcessed = "o" }
if (strProcessed == "") {strProcessed = "p"} if strProcessed == "" { strProcessed = "p" }
if (strProcessed == "") {strProcessed = "q"} if strProcessed == "" { strProcessed = "q" }
if (strProcessed == "") {strProcessed = "r"} if strProcessed == "" { strProcessed = "r" }
if (strProcessed == "") {strProcessed = "s"} if strProcessed == "" { strProcessed = "s" }
if (strProcessed == "") {strProcessed = "t"} if strProcessed == "" { strProcessed = "t" }
if (strProcessed == "") {strProcessed = "u"} if strProcessed == "" { strProcessed = "u" }
if (strProcessed == "") {strProcessed = "v"} if strProcessed == "" { strProcessed = "v" }
if (strProcessed == "") {strProcessed = "w"} if strProcessed == "" { strProcessed = "w" }
if (strProcessed == "") {strProcessed = "x"} if strProcessed == "" { strProcessed = "x" }
if (strProcessed == "") {strProcessed = "y"} if strProcessed == "" { strProcessed = "y" }
if (strProcessed == "") {strProcessed = "z"} 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 = "<"} if strProcessed == "" { strProcessed = "<" }
if (strProcessed == "") {strProcessed = ">"} if strProcessed == "" { strProcessed = ">" }
// SHIFT // 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 = "*" }
if (strProcessed == "") {strProcessed = "("} if strProcessed == "" { strProcessed = "(" }
if (strProcessed == "") {strProcessed = ")"} if strProcessed == "" { strProcessed = ")" }
// Alt // Alt
if (strProcessed == "") {strProcessed = "-"} if strProcessed == "" { strProcessed = "-" }
// Apple // Apple
if mgrPrefs.basisKeyboardLayout == "com.apple.keylayout.ZhuyinEten" { 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 = "+" }
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
@ -135,9 +142,11 @@ class InputState: NSObject {
} }
@objc var attributedString: NSAttributedString { @objc var attributedString: NSAttributedString {
let attributedSting = NSAttributedString(string: composingBuffer, attributes: [ let attributedSting = NSAttributedString(
string: composingBuffer,
attributes: [
.underlineStyle: NSUnderlineStyle.single.rawValue, .underlineStyle: NSUnderlineStyle.single.rawValue,
.markedClauseSegment: 0 .markedClauseSegment: 0,
]) ])
return attributedSting return attributedSting
} }
@ -162,15 +171,19 @@ class InputState: NSObject {
@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(
red: 0.55, green: 0.00, blue: 0.00, alpha: 1.00)
TooltipController.textColor = NSColor.white TooltipController.textColor = NSColor.white
return NSLocalizedString("⚠︎ Unhandlable: Chars and Readings in buffer doesn't match.", comment: "") 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 { if markedRange.length == 0 {
return "" return ""
@ -178,29 +191,49 @@ class InputState: NSObject {
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 (exactEnd, _) = (composingBuffer as NSString).characterIndex(
from: markedRange.location + markedRange.length)
let selectedReadings = readings[exactBegin..<exactEnd] let selectedReadings = readings[exactBegin..<exactEnd]
let joined = selectedReadings.joined(separator: "-") let joined = selectedReadings.joined(separator: "-")
let exist = mgrLangModel.checkIfUserPhraseExist(userPhrase: text, mode: ctlInputMethod.currentKeyHandler.inputMode, key: joined) let exist = mgrLangModel.checkIfUserPhraseExist(
userPhrase: text, mode: ctlInputMethod.currentKeyHandler.inputMode, key: joined)
if exist { if exist {
deleteTargetExists = exist deleteTargetExists = exist
TooltipController.backgroundColor = NSColor(red: 0.00, green: 0.18, blue: 0.13, alpha: 1.00) TooltipController.backgroundColor = NSColor(
TooltipController.textColor = NSColor(red: 0.00, green: 1.00, blue: 0.74, alpha: 1.00) red: 0.00, green: 0.18, blue: 0.13, alpha: 1.00)
return String(format: NSLocalizedString("\"%@\" already exists: ↩ to boost, ⇧⌘↩ to exclude.", comment: ""), text) TooltipController.textColor = NSColor(
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.backgroundColor = NSColor(
red: 0.18, green: 0.18, blue: 0.18, alpha: 1.00)
TooltipController.textColor = NSColor.white TooltipController.textColor = NSColor.white
return String(format: NSLocalizedString("\"%@\" selected. ↩ to add user phrase.", comment: ""), text) return String(
format: NSLocalizedString("\"%@\" selected. ↩ to add user phrase.", comment: ""),
text)
} }
@objc var tooltipForInputting: String = "" @objc var tooltipForInputting: String = ""
@ -219,18 +252,23 @@ class InputState: NSObject {
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, .underlineStyle: NSUnderlineStyle.single.rawValue,
.markedClauseSegment: 0 .markedClauseSegment: 0,
], range: NSRange(location: 0, length: markedRange.location)) ], range: NSRange(location: 0, length: markedRange.location))
attributedSting.setAttributes([ attributedSting.setAttributes(
[
.underlineStyle: NSUnderlineStyle.thick.rawValue, .underlineStyle: NSUnderlineStyle.thick.rawValue,
.markedClauseSegment: 1 .markedClauseSegment: 1,
], range: markedRange) ], range: markedRange)
attributedSting.setAttributes([ attributedSting.setAttributes(
[
.underlineStyle: NSUnderlineStyle.single.rawValue, .underlineStyle: NSUnderlineStyle.single.rawValue,
.markedClauseSegment: 2 .markedClauseSegment: 2,
], range: NSRange(location: end, ],
range: NSRange(
location: end,
length: (composingBuffer as NSString).length - end)) length: (composingBuffer as NSString).length - end))
return attributedSting return attributedSting
} }
@ -262,31 +300,42 @@ class InputState: NSObject {
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 (exactEnd, _) = (composingBuffer as NSString).characterIndex(
from: markedRange.location + markedRange.length)
let selectedReadings = readings[exactBegin..<exactEnd] let selectedReadings = readings[exactBegin..<exactEnd]
let joined = selectedReadings.joined(separator: "-") let joined = selectedReadings.joined(separator: "-")
return mgrLangModel.checkIfUserPhraseExist(userPhrase: text, mode: ctlInputMethod.currentKeyHandler.inputMode, key: joined) == true 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 (exactEnd, _) = (composingBuffer as NSString).characterIndex(
from: markedRange.location + markedRange.length)
let selectedReadings = readings[exactBegin..<exactEnd] let selectedReadings = readings[exactBegin..<exactEnd]
let joined = selectedReadings.joined(separator: "-") let joined = selectedReadings.joined(separator: "-")
return "\(text) \(joined)" 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 (exactBegin, _) = (composingBuffer as NSString).characterIndex(
from: markedRange.location)
let (exactEnd, _) = (composingBuffer as NSString).characterIndex(
from: markedRange.location + markedRange.length)
let selectedReadings = readings[exactBegin..<exactEnd] let selectedReadings = readings[exactBegin..<exactEnd]
let joined = selectedReadings.joined(separator: "-") let joined = selectedReadings.joined(separator: "-")
let convertedMark = "#𝙊𝙥𝙚𝙣𝘾𝘾" let convertedMark = "#𝙊𝙥𝙚𝙣𝘾𝘾"
@ -309,9 +358,11 @@ class InputState: NSObject {
} }
@objc var attributedString: NSAttributedString { @objc var attributedString: NSAttributedString {
let attributedSting = NSAttributedString(string: composingBuffer, attributes: [ let attributedSting = NSAttributedString(
string: composingBuffer,
attributes: [
.underlineStyle: NSUnderlineStyle.single.rawValue, .underlineStyle: NSUnderlineStyle.single.rawValue,
.markedClauseSegment: 0 .markedClauseSegment: 0,
]) ])
return attributedSting return attributedSting
} }
@ -347,7 +398,9 @@ class InputState: NSObject {
@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 {
@ -373,34 +426,53 @@ class InputState: NSObject {
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 catCircledKanjis = String(
format: NSLocalizedString("catCircledKanjis", comment: ""))
@objc static let catCircledKataKana = String(
format: NSLocalizedString("catCircledKataKana", comment: ""))
@objc static let catBracketKanjis = String(
format: NSLocalizedString("catBracketKanjis", comment: ""))
@objc static let catSingleTableLines = String(
format: NSLocalizedString("catSingleTableLines", comment: ""))
@objc static let catDoubleTableLines = String(
format: NSLocalizedString("catDoubleTableLines", comment: ""))
@objc static let catFillingBlocks = String(
format: NSLocalizedString("catFillingBlocks", comment: ""))
@objc static let catLineSegments = String(
format: NSLocalizedString("catLineSegments", comment: ""))
@objc static let root: SymbolNode = SymbolNode("/", [ @objc static let root: SymbolNode = SymbolNode(
"/",
[
SymbolNode(""), SymbolNode(""),
SymbolNode(catCommonSymbols, symbols: ",、。.?!;:‧‥﹐﹒˙·‘’“”〝〞‵′〃~$%@&#*"), SymbolNode(catCommonSymbols, symbols: ",、。.?!;:‧‥﹐﹒˙·‘’“”〝〞‵′〃~$%@&#*"),
SymbolNode(catHoriBrackets, symbols: "()「」〔〕{}〈〉『』《》【】﹙﹚﹝﹞﹛﹜"), SymbolNode(catHoriBrackets, symbols: "()「」〔〕{}〈〉『』《》【】﹙﹚﹝﹞﹛﹜"),
SymbolNode(catVertBrackets, symbols: "︵︶﹁﹂︹︺︷︸︿﹀﹃﹄︽︾︻︼"), SymbolNode(catVertBrackets, symbols: "︵︶﹁﹂︹︺︷︸︿﹀﹃﹄︽︾︻︼"),
SymbolNode(catGreekLetters, symbols:"αβγδεζηθικλμνξοπρστυφχψωΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ"), SymbolNode(
catGreekLetters, symbols: "αβγδεζηθικλμνξοπρστυφχψωΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ"),
SymbolNode(catMathSymbols, symbols: "+-×÷=≠≒∞±√<>﹤﹥≦≧∩∪ˇ⊥∠∟⊿㏒㏑∫∮∵∴╳﹢"), SymbolNode(catMathSymbols, symbols: "+-×÷=≠≒∞±√<>﹤﹥≦≧∩∪ˇ⊥∠∟⊿㏒㏑∫∮∵∴╳﹢"),
SymbolNode(catCurrencyUnits, symbols: "$€¥¢£₽₨₩฿₺₮₱₭₴₦৲৳૱௹﷼₹₲₪₡₫៛₵₢₸₤₳₥₠₣₰₧₯₶₷"), SymbolNode(catCurrencyUnits, symbols: "$€¥¢£₽₨₩฿₺₮₱₭₴₦৲৳૱௹﷼₹₲₪₡₫៛₵₢₸₤₳₥₠₣₰₧₯₶₷"),
SymbolNode(catSpecialSymbols, symbols: "↑↓←→↖↗↙↘↺⇧⇩⇦⇨⇄⇆⇅⇵↻◎○●⊕⊙※△▲☆★◇◆□■▽▼§¥〒¢£♀♂↯"), SymbolNode(catSpecialSymbols, symbols: "↑↓←→↖↗↙↘↺⇧⇩⇦⇨⇄⇆⇅⇵↻◎○●⊕⊙※△▲☆★◇◆□■▽▼§¥〒¢£♀♂↯"),
SymbolNode(catUnicodeSymbols, symbols: "♨☀☁☂☃♠♥♣♦♩♪♫♬☺☻"), SymbolNode(catUnicodeSymbols, symbols: "♨☀☁☂☃♠♥♣♦♩♪♫♬☺☻"),
SymbolNode(catCircledKanjis, symbols: "㊟㊞㊚㊛㊊㊋㊌㊍㊎㊏㊐㊑㊒㊓㊔㊕㊖㊗︎㊘㊙︎㊜㊝㊠㊡㊢㊣㊤㊥㊦㊧㊨㊩㊪㊫㊬㊭㊮㊯㊰🈚︎🈯︎"), SymbolNode(catCircledKanjis, symbols: "㊟㊞㊚㊛㊊㊋㊌㊍㊎㊏㊐㊑㊒㊓㊔㊕㊖㊗︎㊘㊙︎㊜㊝㊠㊡㊢㊣㊤㊥㊦㊧㊨㊩㊪㊫㊬㊭㊮㊯㊰🈚︎🈯︎"),
SymbolNode(catCircledKataKana, symbols:"㋐㋑㋒㋓㋔㋕㋖㋗㋘㋙㋚㋛㋜㋝㋞㋟㋠㋡㋢㋣㋤㋥㋦㋧㋨㋩㋪㋫㋬㋭㋮㋯㋰㋱㋲㋳㋴㋵㋶㋷㋸㋹㋺㋻㋼㋾"), SymbolNode(
catCircledKataKana, symbols: "㋐㋑㋒㋓㋔㋕㋖㋗㋘㋙㋚㋛㋜㋝㋞㋟㋠㋡㋢㋣㋤㋥㋦㋧㋨㋩㋪㋫㋬㋭㋮㋯㋰㋱㋲㋳㋴㋵㋶㋷㋸㋹㋺㋻㋼㋾"),
SymbolNode(catBracketKanjis, symbols: "㈪㈫㈬㈭㈮㈯㈰㈱㈲㈳㈴㈵㈶㈷㈸㈹㈺㈻㈼㈽㈾㈿㉀㉁㉂㉃"), SymbolNode(catBracketKanjis, symbols: "㈪㈫㈬㈭㈮㈯㈰㈱㈲㈳㈴㈵㈶㈷㈸㈹㈺㈻㈼㈽㈾㈿㉀㉁㉂㉃"),
SymbolNode(catSingleTableLines, symbols: "├─┼┴┬┤┌┐╞═╪╡│▕└┘╭╮╰╯"), SymbolNode(catSingleTableLines, symbols: "├─┼┴┬┤┌┐╞═╪╡│▕└┘╭╮╰╯"),
SymbolNode(catDoubleTableLines, symbols: "╔╦╗╠═╬╣╓╥╖╒╤╕║╚╩╝╟╫╢╙╨╜╞╪╡╘╧╛"), SymbolNode(catDoubleTableLines, 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:));

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
@ -46,7 +53,7 @@ import Cocoa
// 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.
} }
@ -66,9 +73,13 @@ class keyParser: NSObject {
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(
inputText: String?, keyCode: UInt16, charCode: UInt16, flags: NSEvent.ModifierFlags,
isVerticalMode: Bool, inputTextIgnoringModifiers: String? = nil
) {
let inputText = AppleKeyboardConverter.cnvStringApple2ABC(inputText ?? "") let inputText = AppleKeyboardConverter.cnvStringApple2ABC(inputText ?? "")
let inputTextIgnoringModifiers = AppleKeyboardConverter.cnvStringApple2ABC(inputTextIgnoringModifiers ?? inputText) let inputTextIgnoringModifiers = AppleKeyboardConverter.cnvStringApple2ABC(
inputTextIgnoringModifiers ?? inputText)
self.inputText = inputText self.inputText = inputText
self.inputTextIgnoringModifiers = inputTextIgnoringModifiers self.inputTextIgnoringModifiers = inputTextIgnoringModifiers
self.keyCode = keyCode self.keyCode = keyCode
@ -76,7 +87,8 @@ class keyParser: NSObject {
self.flags = flags self.flags = flags
self.isFlagChanged = false self.isFlagChanged = false
useVerticalMode = isVerticalMode useVerticalMode = isVerticalMode
emacsKey = EmacsKeyHelper.detect(charCode: AppleKeyboardConverter.cnvApple2ABC(charCode), flags: flags) emacsKey = EmacsKeyHelper.detect(
charCode: AppleKeyboardConverter.cnvApple2ABC(charCode), flags: flags)
cursorForwardKey = useVerticalMode ? .down : .right cursorForwardKey = useVerticalMode ? .down : .right
cursorBackwardKey = useVerticalMode ? .up : .left cursorBackwardKey = useVerticalMode ? .up : .left
extraChooseCandidateKey = useVerticalMode ? .left : .down extraChooseCandidateKey = useVerticalMode ? .left : .down
@ -88,7 +100,8 @@ class keyParser: NSObject {
@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(
event.charactersIgnoringModifiers ?? "")
keyCode = event.keyCode keyCode = event.keyCode
flags = event.modifierFlags flags = event.modifierFlags
isFlagChanged = (event.type == .flagsChanged) ? true : false isFlagChanged = (event.type == .flagsChanged) ? true : false
@ -101,7 +114,8 @@ class keyParser: NSObject {
return first return first
}() }()
self.charCode = AppleKeyboardConverter.cnvApple2ABC(charCode) self.charCode = AppleKeyboardConverter.cnvApple2ABC(charCode)
emacsKey = EmacsKeyHelper.detect(charCode: AppleKeyboardConverter.cnvApple2ABC(charCode), flags: event.modifierFlags) emacsKey = EmacsKeyHelper.detect(
charCode: AppleKeyboardConverter.cnvApple2ABC(charCode), flags: event.modifierFlags)
cursorForwardKey = useVerticalMode ? .down : .right cursorForwardKey = useVerticalMode ? .down : .right
cursorBackwardKey = useVerticalMode ? .up : .left cursorBackwardKey = useVerticalMode ? .up : .left
extraChooseCandidateKey = useVerticalMode ? .left : .down extraChooseCandidateKey = useVerticalMode ? .left : .down
@ -114,8 +128,10 @@ class keyParser: NSObject {
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 {
@ -166,7 +182,8 @@ class keyParser: NSObject {
} }
@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 {
@ -270,6 +287,6 @@ class EmacsKeyHelper: NSObject {
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,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.
*/ */
#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_;
} }
@ -38,18 +46,22 @@ KeyValueBlobReader::State KeyValueBlobReader::Next(KeyValue* out)
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_)
{
if (!f(*current_))
{ {
while (current_ != end_ && *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_)
{
if (f(*current_))
{ {
while (current_ != end_ && *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: public:
enum class State : int { 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,15 +64,12 @@ 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)
{ {
} }
@ -75,9 +82,7 @@ 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)
{ {
} }

View File

@ -1,25 +1,32 @@
// 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.
/// ///
@ -29,7 +36,7 @@ public extension NSString {
/// 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() {
@ -41,7 +48,7 @@ public extension NSString {
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
@ -49,7 +56,7 @@ public extension NSString {
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
@ -57,11 +64,11 @@ public extension NSString {
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))
} }

View File

@ -1,25 +1,31 @@
// 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
private extension String { extension String {
mutating func selfReplace(_ strOf: String, _ strWith: String = "") { fileprivate mutating func selfReplace(_ strOf: String, _ strWith: String = "") {
self = self.replacingOccurrences(of: strOf, with: strWith) self = self.replacingOccurrences(of: strOf, with: strWith)
} }
} }

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
@ -48,15 +55,20 @@ public class FSEventStreamHelper : NSObject {
} }
var context = FSEventStreamContext() var context = FSEventStreamContext()
context.info = Unmanaged.passUnretained(self).toOpaque() context.info = Unmanaged.passUnretained(self).toOpaque()
guard let stream = FSEventStreamCreate(nil, { guard
let stream = FSEventStreamCreate(
nil,
{
(stream, clientCallBackInfo, eventCount, eventPaths, eventFlags, eventIds) in (stream, clientCallBackInfo, eventCount, eventPaths, eventFlags, eventIds) in
let helper = Unmanaged<FSEventStreamHelper>.fromOpaque(clientCallBackInfo!).takeUnretainedValue() let helper = Unmanaged<FSEventStreamHelper>.fromOpaque(clientCallBackInfo!)
.takeUnretainedValue()
let pathsBase = eventPaths.assumingMemoryBound(to: UnsafePointer<CChar>.self) let pathsBase = eventPaths.assumingMemoryBound(to: UnsafePointer<CChar>.self)
let pathsPtr = UnsafeBufferPointer(start: pathsBase, count: eventCount) let pathsPtr = UnsafeBufferPointer(start: pathsBase, count: eventCount)
let flagsPtr = UnsafeBufferPointer(start: eventFlags, count: eventCount) let flagsPtr = UnsafeBufferPointer(start: eventFlags, count: eventCount)
let eventIDsPtr = UnsafeBufferPointer(start: eventIds, count: eventCount) let eventIDsPtr = UnsafeBufferPointer(start: eventIds, count: eventCount)
let events = (0..<eventCount).map { let events = (0..<eventCount).map {
FSEventStreamHelper.Event(path: String(cString: pathsPtr[$0]), FSEventStreamHelper.Event(
path: String(cString: pathsPtr[$0]),
flags: flagsPtr[$0], flags: flagsPtr[$0],
id: eventIDsPtr[$0]) id: eventIDsPtr[$0])
} }
@ -67,7 +79,8 @@ public class FSEventStreamHelper : NSObject {
UInt64(kFSEventStreamEventIdSinceNow), UInt64(kFSEventStreamEventIdSinceNow),
1.0, 1.0,
FSEventStreamCreateFlags(kFSEventStreamCreateFlagNone) FSEventStreamCreateFlags(kFSEventStreamCreateFlagNone)
) else { )
else {
return false return false
} }
@ -89,4 +102,3 @@ public class FSEventStreamHelper : NSObject {
self.stream = nil self.stream = nil
} }
} }

View File

@ -1,36 +1,43 @@
// 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
{ {

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;
} }
@ -49,32 +60,45 @@ bool LMConsolidator::FixEOF(const char *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;
} }
@ -87,17 +111,22 @@ bool LMConsolidator::ConsolidateContent(const char *path, bool shouldCheckPragma
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();
@ -109,24 +138,36 @@ bool LMConsolidator::ConsolidateContent(const char *path, bool shouldCheckPragma
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,26 +1,40 @@
// 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: -
@objc static var areWeUsingOurOwnPhraseEditor: Bool = false
// MARK: - ctlInputMethod
static func getInputMode() -> InputMode {
return ctlInputMethod.currentKeyHandler.inputMode
}
// MARK: - Print debug information to the console. // MARK: - Print debug information to the console.
@objc static func prtDebugIntel(_ strPrint: String) { @objc static func prtDebugIntel(_ strPrint: String) {
@ -50,12 +64,15 @@ import Cocoa
// MARK: - System Dark Mode Status Detector. // MARK: - System Dark Mode Status Detector.
@objc static func isDarkMode() -> Bool { @objc static func isDarkMode() -> Bool {
if #available(macOS 10.15, *) { if #available(macOS 10.15, *) {
let appearanceDescription = NSApplication.shared.effectiveAppearance.debugDescription.lowercased() let appearanceDescription = NSApplication.shared.effectiveAppearance.debugDescription
.lowercased()
if appearanceDescription.contains("dark") { if appearanceDescription.contains("dark") {
return true return true
} }
} else if #available(macOS 10.14, *) { } else if #available(macOS 10.14, *) {
if let appleInterfaceStyle = UserDefaults.standard.object(forKey: "AppleInterfaceStyle") as? String { if let appleInterfaceStyle = UserDefaults.standard.object(forKey: "AppleInterfaceStyle")
as? String
{
if appleInterfaceStyle.lowercased().contains("dark") { if appleInterfaceStyle.lowercased().contains("dark") {
return true return true
} }
@ -64,12 +81,37 @@ import Cocoa
return false return false
} }
// MARK: - Open a phrase data file.
static func openPhraseFile(userFileAt path: String) {
func checkIfUserFilesExist() -> Bool {
if !mgrLangModel.checkIfUserLanguageModelFilesExist() {
let content = String(
format: NSLocalizedString(
"Please check the permission at \"%@\".", comment: ""),
mgrLangModel.dataFolderPath(isDefaultFolder: false))
ctlNonModalAlertWindow.shared.show(
title: NSLocalizedString("Unable to create the user phrase file.", comment: ""),
content: content, confirmButtonTitle: NSLocalizedString("OK", comment: ""),
cancelButtonTitle: nil, cancelAsDefault: false, delegate: nil)
NSApp.setActivationPolicy(.accessory)
return false
}
return true
}
if !checkIfUserFilesExist() {
return
}
NSWorkspace.shared.openFile(path, withApplication: "vChewingPhraseEditor")
}
// MARK: - Trash a file if it exists. // MARK: - Trash a file if it exists.
@discardableResult static func trashTargetIfExists(_ path: String) -> Bool { @discardableResult static func trashTargetIfExists(_ path: String) -> Bool {
do { do {
if FileManager.default.fileExists(atPath: path) { if FileManager.default.fileExists(atPath: path) {
// //
try FileManager.default.trashItem(at: URL(fileURLWithPath: path), resultingItemURL: nil) try FileManager.default.trashItem(
at: URL(fileURLWithPath: path), resultingItemURL: nil)
} else { } else {
NSLog("Item doesn't exist: \(path)") NSLog("Item doesn't exist: \(path)")
} }
@ -79,7 +121,8 @@ import Cocoa
} }
return true return true
} }
// MARK: - Uninstalling the input method.
// MARK: - Uninstall the input method.
@discardableResult static func uninstall(isSudo: Bool = false, selfKill: Bool = true) -> Int32 { @discardableResult static func uninstall(isSudo: Bool = false, selfKill: Bool = true) -> Int32 {
// Bundle.main.bundleURL便使 sudo // Bundle.main.bundleURL便使 sudo
guard let bundleID = Bundle.main.bundleIdentifier else { guard let bundleID = Bundle.main.bundleIdentifier else {
@ -89,17 +132,29 @@ import Cocoa
let kTargetBin = "vChewing" let kTargetBin = "vChewing"
let kTargetBundle = "/vChewing.app" let kTargetBundle = "/vChewing.app"
let pathLibrary = isSudo ? "/Library" : FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask)[0].path let pathLibrary =
let pathIMELibrary = isSudo ? "/Library/Input Methods" : FileManager.default.urls(for: .inputMethodsDirectory, in: .userDomainMask)[0].path 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 pathUnitKeyboardLayouts = "/Keyboard Layouts"
let arrKeyLayoutFiles = ["/vChewing ETen.keylayout", "/vChewingKeyLayout.bundle", "/vChewing MiTAC.keylayout", "/vChewing IBM.keylayout", "/vChewing FakeSeigyou.keylayout", "/vChewing Dachen.keylayout"] let arrKeyLayoutFiles = [
"/vChewing ETen.keylayout", "/vChewingKeyLayout.bundle", "/vChewing MiTAC.keylayout",
"/vChewing IBM.keylayout", "/vChewing FakeSeigyou.keylayout",
"/vChewing Dachen.keylayout",
]
// //
for objPath in arrKeyLayoutFiles { for objPath in arrKeyLayoutFiles {
let objFullPath = pathLibrary + pathUnitKeyboardLayouts + objPath let objFullPath = pathLibrary + pathUnitKeyboardLayouts + objPath
if !IME.trashTargetIfExists(objFullPath) { return -1 } if !IME.trashTargetIfExists(objFullPath) { return -1 }
} }
if CommandLine.arguments.count > 2 && CommandLine.arguments[2] == "--all" && CommandLine.arguments[1] == "uninstall" { if CommandLine.arguments.count > 2 && CommandLine.arguments[2] == "--all"
&& CommandLine.arguments[1] == "uninstall"
{
// 使 // 使
// 使 symbol link // 使 symbol link
// symbol link // symbol link
@ -127,12 +182,14 @@ import Cocoa
var maybeInputSource = InputSourceHelper.inputSource(for: bundleID) var maybeInputSource = InputSourceHelper.inputSource(for: bundleID)
if maybeInputSource == nil { if maybeInputSource == nil {
NSLog("Registering input source \(bundleID) at \(bundleUrl.absoluteString)"); NSLog("Registering input source \(bundleID) at \(bundleUrl.absoluteString)")
// then register // then register
let status = InputSourceHelper.registerTnputSource(at: bundleUrl) let status = InputSourceHelper.registerTnputSource(at: bundleUrl)
if !status { if !status {
NSLog("Fatal error: Cannot register input source \(bundleID) at \(bundleUrl.absoluteString).") NSLog(
"Fatal error: Cannot register input source \(bundleID) at \(bundleUrl.absoluteString)."
)
return -1 return -1
} }
@ -159,10 +216,12 @@ import Cocoa
if CommandLine.arguments.count > 2 && CommandLine.arguments[2] == "--all" { if CommandLine.arguments.count > 2 && CommandLine.arguments[2] == "--all" {
let enabled = InputSourceHelper.enableAllInputMode(for: bundleID) 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") NSLog(
enabled
? "All input sources enabled for \(bundleID)"
: "Cannot enable all input sources for \(bundleID), but this is ignored")
} }
return 0 return 0
} }
} }

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 Cocoa
import Carbon import Carbon
import Cocoa
public class InputSourceHelper: NSObject { public class InputSourceHelper: NSObject {
@ -32,7 +39,9 @@ public class InputSourceHelper: NSObject {
} }
@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)
-> TISInputSource?
{
let stringID = CFStringGetTypeID() let stringID = CFStringGetTypeID()
for source in allInstalledInputSources() { for source in allInstalledInputSources() {
if let propertyPtr = TISGetInputSourceProperty(source, propertyKey) { if let propertyPtr = TISGetInputSourceProperty(source, propertyKey) {
@ -74,7 +83,8 @@ public class InputSourceHelper: NSObject {
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)
else {
continue continue
} }
let bundleID = Unmanaged<CFString>.fromOpaque(bundleIDPtr).takeUnretainedValue() let bundleID = Unmanaged<CFString>.fromOpaque(bundleIDPtr).takeUnretainedValue()
@ -94,14 +104,18 @@ public class InputSourceHelper: NSObject {
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)
else {
continue continue
} }
let inputsSourceBundleID = Unmanaged<CFString>.fromOpaque(bundleIDPtr).takeUnretainedValue() let inputsSourceBundleID = Unmanaged<CFString>.fromOpaque(bundleIDPtr)
.takeUnretainedValue()
let inputsSourceModeID = Unmanaged<CFString>.fromOpaque(modePtr).takeUnretainedValue() let inputsSourceModeID = Unmanaged<CFString>.fromOpaque(modePtr).takeUnretainedValue()
if modeID == String(inputsSourceModeID) && bundleID == String(inputsSourceBundleID) { if modeID == String(inputsSourceModeID) && bundleID == String(inputsSourceBundleID) {
let enabled = enable(inputSource: source) let enabled = enable(inputSource: source)
print("Attempt to enable input source of mode: \(modeID), bundle ID: \(bundleID), result: \(enabled)") print(
"Attempt to enable input source of mode: \(modeID), bundle ID: \(bundleID), result: \(enabled)"
)
return enabled return enabled
} }
@ -124,4 +138,3 @@ public class InputSourceHelper: NSObject {
} }
} }

View File

@ -1,48 +1,49 @@
// 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
import InputMethodKit import InputMethodKit
private extension Bool {
var state: NSControl.StateValue {
self ? .on : .off
}
}
private let kMinKeyLabelSize: CGFloat = 10 private let kMinKeyLabelSize: CGFloat = 10
private var gCurrentCandidateController: CandidateController? private var ctlCandidateCurrent: ctlCandidate?
private extension CandidateController { extension ctlCandidate {
static let horizontal = HorizontalCandidateController() fileprivate static let horizontal = ctlCandidateHorizontal()
static let vertical = VerticalCandidateController() fileprivate static let vertical = ctlCandidateVertical()
} }
@objc(ctlInputMethod) @objc(ctlInputMethod)
class ctlInputMethod: IMKInputController { class ctlInputMethod: IMKInputController {
@objc static let kIMEModeCHS = "org.atelierInmu.inputmethod.vChewing.IMECHS"; @objc static let kIMEModeCHS = "org.atelierInmu.inputmethod.vChewing.IMECHS"
@objc static let kIMEModeCHT = "org.atelierInmu.inputmethod.vChewing.IMECHT"; @objc static let kIMEModeCHT = "org.atelierInmu.inputmethod.vChewing.IMECHT"
@objc static let kIMEModeNULL = "org.atelierInmu.inputmethod.vChewing.IMENULL"; @objc static let kIMEModeNULL = "org.atelierInmu.inputmethod.vChewing.IMENULL"
@objc static var areWeDeleting = false; @objc static var areWeDeleting = false
private static let tooltipController = TooltipController() private static let tooltipController = TooltipController()
@ -75,78 +76,6 @@ class ctlInputMethod: IMKInputController {
keyHandler.delegate = self keyHandler.delegate = self
} }
override func menu() -> NSMenu! {
let optionKeyPressed = NSEvent.modifierFlags.contains(.option)
let menu = NSMenu(title: "Input Method Menu")
let useSCPCTypingModeItem = menu.addItem(withTitle: NSLocalizedString("Per-Char Select Mode", comment: ""), action: #selector(toggleSCPCTypingMode(_:)), keyEquivalent: "P")
useSCPCTypingModeItem.keyEquivalentModifierMask = [.command, .control]
useSCPCTypingModeItem.state = mgrPrefs.useSCPCTypingMode.state
let useCNS11643SupportItem = menu.addItem(withTitle: NSLocalizedString("CNS11643 Mode", comment: ""), action: #selector(toggleCNS11643Enabled(_:)), keyEquivalent: "L")
useCNS11643SupportItem.keyEquivalentModifierMask = [.command, .control]
useCNS11643SupportItem.state = mgrPrefs.cns11643Enabled.state
if keyHandler.inputMode == InputMode.imeModeCHT {
let chineseConversionItem = menu.addItem(withTitle: NSLocalizedString("Force KangXi Writing", comment: ""), action: #selector(toggleChineseConverter(_:)), keyEquivalent: "K")
chineseConversionItem.keyEquivalentModifierMask = [.command, .control]
chineseConversionItem.state = mgrPrefs.chineseConversionEnabled.state
let shiftJISConversionItem = menu.addItem(withTitle: NSLocalizedString("JIS Shinjitai Output", comment: ""), action: #selector(toggleShiftJISShinjitaiOutput(_:)), keyEquivalent: "J")
shiftJISConversionItem.keyEquivalentModifierMask = [.command, .control]
shiftJISConversionItem.state = mgrPrefs.shiftJISShinjitaiOutputEnabled.state
}
let halfWidthPunctuationItem = menu.addItem(withTitle: NSLocalizedString("Half-Width Punctuation Mode", comment: ""), action: #selector(toggleHalfWidthPunctuation(_:)), keyEquivalent: "H")
halfWidthPunctuationItem.keyEquivalentModifierMask = [.command, .control]
halfWidthPunctuationItem.state = mgrPrefs.halfWidthPunctuationEnabled.state
let userAssociatedPhrasesItem = menu.addItem(withTitle: NSLocalizedString("Per-Char Associated Phrases", comment: ""), action: #selector(toggleAssociatedPhrasesEnabled(_:)), keyEquivalent: "O")
userAssociatedPhrasesItem.keyEquivalentModifierMask = [.command, .control]
userAssociatedPhrasesItem.state = mgrPrefs.associatedPhrasesEnabled.state
if optionKeyPressed {
let phaseReplacementItem = menu.addItem(withTitle: NSLocalizedString("Use Phrase Replacement", comment: ""), action: #selector(togglePhraseReplacement(_:)), keyEquivalent: "")
phaseReplacementItem.state = mgrPrefs.phraseReplacementEnabled.state
let toggleSymbolInputItem = menu.addItem(withTitle: NSLocalizedString("Symbol & Emoji Input", comment: ""), action: #selector(toggleSymbolEnabled(_:)), keyEquivalent: "")
toggleSymbolInputItem.state = mgrPrefs.symbolInputEnabled.state
}
menu.addItem(NSMenuItem.separator()) // ---------------------
menu.addItem(withTitle: NSLocalizedString("Open User Data Folder", comment: ""), action: #selector(openUserDataFolder(_:)), keyEquivalent: "")
menu.addItem(withTitle: NSLocalizedString("Edit User Phrases…", comment: ""), action: #selector(openUserPhrases(_:)), keyEquivalent: "")
if optionKeyPressed {
menu.addItem(withTitle: NSLocalizedString("Edit Excluded Phrases…", comment: ""), action: #selector(openExcludedPhrases(_:)), keyEquivalent: "")
menu.addItem(withTitle: NSLocalizedString("Edit Phrase Replacement Table…", comment: ""), action: #selector(openPhraseReplacement(_:)), keyEquivalent: "")
menu.addItem(withTitle: NSLocalizedString("Edit Associated Phrases…", comment: ""), action: #selector(openAssociatedPhrases(_:)), keyEquivalent: "")
menu.addItem(withTitle: NSLocalizedString("Edit User Symbol & Emoji Data…", comment: ""), action: #selector(openUserSymbols(_:)), keyEquivalent: "")
}
if (optionKeyPressed || !mgrPrefs.shouldAutoReloadUserDataFiles) {
menu.addItem(withTitle: NSLocalizedString("Reload User Phrases", comment: ""), action: #selector(reloadUserPhrases(_:)), keyEquivalent: "")
}
menu.addItem(NSMenuItem.separator()) // ---------------------
menu.addItem(withTitle: NSLocalizedString("vChewing Preferences…", comment: ""), action: #selector(showPreferences(_:)), keyEquivalent: "")
if !optionKeyPressed {
menu.addItem(withTitle: NSLocalizedString("Check for Updates…", comment: ""), action: #selector(checkForUpdate(_:)), keyEquivalent: "")
}
menu.addItem(withTitle: NSLocalizedString("Reboot vChewing…", comment: ""), action: #selector(selfTerminate(_:)), keyEquivalent: "")
menu.addItem(withTitle: NSLocalizedString("About vChewing…", comment: ""), action: #selector(showAbout(_:)), keyEquivalent: "")
if optionKeyPressed {
menu.addItem(withTitle: NSLocalizedString("Uninstall vChewing…", comment: ""), action: #selector(selfUninstall(_:)), keyEquivalent: "")
}
// NSMenu modified key
setKeyLayout()
return menu
}
// MARK: - IMKStateSetting protocol methods // MARK: - IMKStateSetting protocol methods
override func activateServer(_ client: Any!) { override func activateServer(_ client: Any!) {
@ -216,13 +145,17 @@ class ctlInputMethod: IMKInputController {
ctlInputMethod.areWeDeleting = event.modifierFlags.contains([.shift, .command]) ctlInputMethod.areWeDeleting = event.modifierFlags.contains([.shift, .command])
var textFrame = NSRect.zero var textFrame = NSRect.zero
let attributes: [AnyHashable: Any]? = (client as? IMKTextInput)?.attributes(forCharacterIndex: 0, lineHeightRectangle: &textFrame) let attributes: [AnyHashable: Any]? = (client as? IMKTextInput)?.attributes(
let useVerticalMode = (attributes?["IMKTextOrientation"] as? NSNumber)?.intValue == 0 || false forCharacterIndex: 0, lineHeightRectangle: &textFrame)
let useVerticalMode =
(attributes?["IMKTextOrientation"] as? NSNumber)?.intValue == 0 || false
if (client as? IMKTextInput)?.bundleIdentifier() == "org.atelierInmu.vChewing.vChewingPhraseEditor" { if (client as? IMKTextInput)?.bundleIdentifier()
ctlInputMethod.areWeUsingOurOwnPhraseEditor = true == "org.atelierInmu.vChewing.vChewingPhraseEditor"
{
IME.areWeUsingOurOwnPhraseEditor = true
} else { } else {
ctlInputMethod.areWeUsingOurOwnPhraseEditor = false IME.areWeUsingOurOwnPhraseEditor = false
} }
let input = keyParser(event: event, isVerticalMode: useVerticalMode) let input = keyParser(event: event, isVerticalMode: useVerticalMode)
@ -234,112 +167,6 @@ class ctlInputMethod: IMKInputController {
} }
return result return result
} }
// MARK: - Menu Items
@objc override func showPreferences(_ sender: Any?) {
(NSApp.delegate as? AppDelegate)?.showPreferences()
NSApp.activate(ignoringOtherApps: true)
}
@objc func toggleSCPCTypingMode(_ sender: Any?) {
NotifierController.notify(message: String(format: "%@%@%@", NSLocalizedString("Per-Char Select Mode", comment: ""), "\n", mgrPrefs.toggleSCPCTypingModeEnabled() ? NSLocalizedString("NotificationSwitchON", comment: "") : NSLocalizedString("NotificationSwitchOFF", comment: "")))
}
@objc func toggleChineseConverter(_ sender: Any?) {
NotifierController.notify(message: String(format: "%@%@%@", NSLocalizedString("Force KangXi Writing", comment: ""), "\n", mgrPrefs.toggleChineseConversionEnabled() ? NSLocalizedString("NotificationSwitchON", comment: "") : NSLocalizedString("NotificationSwitchOFF", comment: "")))
}
@objc func toggleShiftJISShinjitaiOutput(_ sender: Any?) {
NotifierController.notify(message: String(format: "%@%@%@", NSLocalizedString("JIS Shinjitai Output", comment: ""), "\n", mgrPrefs.toggleShiftJISShinjitaiOutputEnabled() ? NSLocalizedString("NotificationSwitchON", comment: "") : NSLocalizedString("NotificationSwitchOFF", comment: "")))
}
@objc func toggleHalfWidthPunctuation(_ sender: Any?) {
NotifierController.notify(message: String(format: "%@%@%@", NSLocalizedString("Half-Width Punctuation Mode", comment: ""), "\n", mgrPrefs.toggleHalfWidthPunctuationEnabled() ? NSLocalizedString("NotificationSwitchON", comment: "") : NSLocalizedString("NotificationSwitchOFF", comment: "")))
}
@objc func toggleCNS11643Enabled(_ sender: Any?) {
NotifierController.notify(message: String(format: "%@%@%@", NSLocalizedString("CNS11643 Mode", comment: ""), "\n", mgrPrefs.toggleCNS11643Enabled() ? NSLocalizedString("NotificationSwitchON", comment: "") : NSLocalizedString("NotificationSwitchOFF", comment: "")))
}
@objc func toggleSymbolEnabled(_ sender: Any?) {
NotifierController.notify(message: String(format: "%@%@%@", NSLocalizedString("Symbol & Emoji Input", comment: ""), "\n", mgrPrefs.toggleSymbolInputEnabled() ? NSLocalizedString("NotificationSwitchON", comment: "") : NSLocalizedString("NotificationSwitchOFF", comment: "")))
}
@objc func toggleAssociatedPhrasesEnabled(_ sender: Any?) {
NotifierController.notify(message: String(format: "%@%@%@", NSLocalizedString("Per-Char Associated Phrases", comment: ""), "\n", mgrPrefs.toggleAssociatedPhrasesEnabled() ? NSLocalizedString("NotificationSwitchON", comment: "") : NSLocalizedString("NotificationSwitchOFF", comment: "")))
}
@objc func togglePhraseReplacement(_ sender: Any?) {
NotifierController.notify(message: String(format: "%@%@%@", NSLocalizedString("Use Phrase Replacement", comment: ""), "\n", mgrPrefs.togglePhraseReplacementEnabled() ? NSLocalizedString("NotificationSwitchON", comment: "") : NSLocalizedString("NotificationSwitchOFF", comment: "")))
}
@objc func selfUninstall(_ sender: Any?) {
(NSApp.delegate as? AppDelegate)?.selfUninstall()
}
@objc func selfTerminate(_ sender: Any?) {
NSApp.terminate(nil)
}
@objc func checkForUpdate(_ sender: Any?) {
(NSApp.delegate as? AppDelegate)?.checkForUpdate(forced: true)
}
private func open(userFileAt path: String) {
func checkIfUserFilesExist() -> Bool {
if !mgrLangModel.checkIfUserLanguageModelFilesExist() {
let content = String(format: NSLocalizedString("Please check the permission at \"%@\".", comment: ""), mgrLangModel.dataFolderPath(isDefaultFolder: false))
ctlNonModalAlertWindow.shared.show(title: NSLocalizedString("Unable to create the user phrase file.", comment: ""), content: content, confirmButtonTitle: NSLocalizedString("OK", comment: ""), cancelButtonTitle: nil, cancelAsDefault: false, delegate: nil)
NSApp.setActivationPolicy(.accessory)
return false
}
return true
}
if !checkIfUserFilesExist() {
return
}
NSWorkspace.shared.openFile(path, withApplication: "vChewingPhraseEditor")
}
@objc func openUserPhrases(_ sender: Any?) {
open(userFileAt: mgrLangModel.userPhrasesDataPath(keyHandler.inputMode))
}
@objc func openUserDataFolder(_ sender: Any?) {
if !mgrLangModel.checkIfUserDataFolderExists() {
return
}
NSWorkspace.shared.openFile(mgrLangModel.dataFolderPath(isDefaultFolder: false), withApplication: "Finder")
}
@objc func openExcludedPhrases(_ sender: Any?) {
open(userFileAt: mgrLangModel.excludedPhrasesDataPath(keyHandler.inputMode))
}
@objc func openUserSymbols(_ sender: Any?) {
open(userFileAt: mgrLangModel.userSymbolDataPath(keyHandler.inputMode))
}
@objc func openPhraseReplacement(_ sender: Any?) {
open(userFileAt: mgrLangModel.phraseReplacementDataPath(keyHandler.inputMode))
}
@objc func openAssociatedPhrases(_ sender: Any?) {
open(userFileAt: mgrLangModel.userAssociatedPhrasesDataPath(keyHandler.inputMode))
}
@objc func reloadUserPhrases(_ sender: Any?) {
mgrLangModel.loadUserPhrases()
mgrLangModel.loadUserPhraseReplacement()
}
@objc func showAbout(_ sender: Any?) {
(NSApp.delegate as? AppDelegate)?.showAbout()
NSApp.activate(ignoringOtherApps: true)
}
} }
// MARK: - State Handling // MARK: - State Handling
@ -393,24 +220,27 @@ extension ctlInputMethod {
if buffer.isEmpty { if buffer.isEmpty {
return return
} }
(client as? IMKTextInput)?.insertText(buffer, replacementRange: NSRange(location: NSNotFound, length: NSNotFound)) (client as? IMKTextInput)?.insertText(
buffer, replacementRange: NSRange(location: NSNotFound, length: NSNotFound))
} }
private func handle(state: InputState.Deactivated, previous: InputState, client: Any?) { private func handle(state: InputState.Deactivated, previous: InputState, client: Any?) {
currentCandidateClient = nil currentCandidateClient = nil
gCurrentCandidateController?.delegate = nil ctlCandidateCurrent?.delegate = nil
gCurrentCandidateController?.visible = false ctlCandidateCurrent?.visible = false
hideTooltip() hideTooltip()
if let previous = previous as? InputState.NotEmpty { if let previous = previous as? InputState.NotEmpty {
commit(text: previous.composingBuffer, client: client) commit(text: previous.composingBuffer, client: client)
} }
(client as? IMKTextInput)?.setMarkedText("", selectionRange: NSMakeRange(0, 0), replacementRange: NSMakeRange(NSNotFound, NSNotFound)) (client as? IMKTextInput)?.setMarkedText(
"", selectionRange: NSMakeRange(0, 0),
replacementRange: NSMakeRange(NSNotFound, NSNotFound))
} }
private func handle(state: InputState.Empty, previous: InputState, client: Any?) { private func handle(state: InputState.Empty, previous: InputState, client: Any?) {
gCurrentCandidateController?.visible = false ctlCandidateCurrent?.visible = false
hideTooltip() hideTooltip()
guard let client = client as? IMKTextInput else { guard let client = client as? IMKTextInput else {
@ -420,22 +250,28 @@ extension ctlInputMethod {
if let previous = previous as? InputState.NotEmpty { if let previous = previous as? InputState.NotEmpty {
commit(text: previous.composingBuffer, client: client) commit(text: previous.composingBuffer, client: client)
} }
client.setMarkedText("", selectionRange: NSMakeRange(0, 0), replacementRange: NSMakeRange(NSNotFound, NSNotFound)) client.setMarkedText(
"", selectionRange: NSMakeRange(0, 0),
replacementRange: NSMakeRange(NSNotFound, NSNotFound))
} }
private func handle(state: InputState.EmptyIgnoringPreviousState, previous: InputState, client: Any!) { private func handle(
gCurrentCandidateController?.visible = false state: InputState.EmptyIgnoringPreviousState, previous: InputState, client: Any!
) {
ctlCandidateCurrent?.visible = false
hideTooltip() hideTooltip()
guard let client = client as? IMKTextInput else { guard let client = client as? IMKTextInput else {
return return
} }
client.setMarkedText("", selectionRange: NSMakeRange(0, 0), replacementRange: NSMakeRange(NSNotFound, NSNotFound)) client.setMarkedText(
"", selectionRange: NSMakeRange(0, 0),
replacementRange: NSMakeRange(NSNotFound, NSNotFound))
} }
private func handle(state: InputState.Committing, previous: InputState, client: Any?) { private func handle(state: InputState.Committing, previous: InputState, client: Any?) {
gCurrentCandidateController?.visible = false ctlCandidateCurrent?.visible = false
hideTooltip() hideTooltip()
guard let client = client as? IMKTextInput else { guard let client = client as? IMKTextInput else {
@ -446,11 +282,13 @@ extension ctlInputMethod {
if !poppedText.isEmpty { if !poppedText.isEmpty {
commit(text: poppedText, client: client) commit(text: poppedText, client: client)
} }
client.setMarkedText("", selectionRange: NSMakeRange(0, 0), replacementRange: NSMakeRange(NSNotFound, NSNotFound)) client.setMarkedText(
"", selectionRange: NSMakeRange(0, 0),
replacementRange: NSMakeRange(NSNotFound, NSNotFound))
} }
private func handle(state: InputState.Inputting, previous: InputState, client: Any?) { private func handle(state: InputState.Inputting, previous: InputState, client: Any?) {
gCurrentCandidateController?.visible = false ctlCandidateCurrent?.visible = false
hideTooltip() hideTooltip()
guard let client = client as? IMKTextInput else { guard let client = client as? IMKTextInput else {
@ -464,14 +302,18 @@ extension ctlInputMethod {
// the selection range is where the cursor is, with the length being 0 and replacement range NSNotFound, // the selection range is where the cursor is, with the length being 0 and replacement range NSNotFound,
// i.e. the client app needs to take care of where to put this composing buffer // i.e. the client app needs to take care of where to put this composing buffer
client.setMarkedText(state.attributedString, selectionRange: NSMakeRange(Int(state.cursorIndex), 0), replacementRange: NSMakeRange(NSNotFound, NSNotFound)) client.setMarkedText(
state.attributedString, selectionRange: NSMakeRange(Int(state.cursorIndex), 0),
replacementRange: NSMakeRange(NSNotFound, NSNotFound))
if !state.tooltip.isEmpty { if !state.tooltip.isEmpty {
show(tooltip: state.tooltip, composingBuffer: state.composingBuffer, cursorIndex: state.cursorIndex, client: client) show(
tooltip: state.tooltip, composingBuffer: state.composingBuffer,
cursorIndex: state.cursorIndex, client: client)
} }
} }
private func handle(state: InputState.Marking, previous: InputState, client: Any?) { private func handle(state: InputState.Marking, previous: InputState, client: Any?) {
gCurrentCandidateController?.visible = false ctlCandidateCurrent?.visible = false
guard let client = client as? IMKTextInput else { guard let client = client as? IMKTextInput else {
hideTooltip() hideTooltip()
return return
@ -479,35 +321,43 @@ extension ctlInputMethod {
// the selection range is where the cursor is, with the length being 0 and replacement range NSNotFound, // the selection range is where the cursor is, with the length being 0 and replacement range NSNotFound,
// i.e. the client app needs to take care of where to put this composing buffer // i.e. the client app needs to take care of where to put this composing buffer
client.setMarkedText(state.attributedString, selectionRange: NSMakeRange(Int(state.cursorIndex), 0), replacementRange: NSMakeRange(NSNotFound, NSNotFound)) client.setMarkedText(
state.attributedString, selectionRange: NSMakeRange(Int(state.cursorIndex), 0),
replacementRange: NSMakeRange(NSNotFound, NSNotFound))
if state.tooltip.isEmpty { if state.tooltip.isEmpty {
hideTooltip() hideTooltip()
} else { } else {
show(tooltip: state.tooltip, composingBuffer: state.composingBuffer, cursorIndex: state.markerIndex, client: client) show(
tooltip: state.tooltip, composingBuffer: state.composingBuffer,
cursorIndex: state.markerIndex, client: client)
} }
} }
private func handle(state: InputState.ChoosingCandidate, previous: InputState, client: Any?) { private func handle(state: InputState.ChoosingCandidate, previous: InputState, client: Any?) {
hideTooltip() hideTooltip()
guard let client = client as? IMKTextInput else { guard let client = client as? IMKTextInput else {
gCurrentCandidateController?.visible = false ctlCandidateCurrent?.visible = false
return return
} }
// the selection range is where the cursor is, with the length being 0 and replacement range NSNotFound, // the selection range is where the cursor is, with the length being 0 and replacement range NSNotFound,
// i.e. the client app needs to take care of where to put this composing buffer // i.e. the client app needs to take care of where to put this composing buffer
client.setMarkedText(state.attributedString, selectionRange: NSMakeRange(Int(state.cursorIndex), 0), replacementRange: NSMakeRange(NSNotFound, NSNotFound)) client.setMarkedText(
state.attributedString, selectionRange: NSMakeRange(Int(state.cursorIndex), 0),
replacementRange: NSMakeRange(NSNotFound, NSNotFound))
show(candidateWindowWith: state, client: client) show(candidateWindowWith: state, client: client)
} }
private func handle(state: InputState.AssociatedPhrases, previous: InputState, client: Any?) { private func handle(state: InputState.AssociatedPhrases, previous: InputState, client: Any?) {
hideTooltip() hideTooltip()
guard let client = client as? IMKTextInput else { guard let client = client as? IMKTextInput else {
gCurrentCandidateController?.visible = false ctlCandidateCurrent?.visible = false
return return
} }
client.setMarkedText("", selectionRange: NSMakeRange(0, 0), replacementRange: NSMakeRange(NSNotFound, NSNotFound)) client.setMarkedText(
"", selectionRange: NSMakeRange(0, 0),
replacementRange: NSMakeRange(NSNotFound, NSNotFound))
show(candidateWindowWith: state, client: client) show(candidateWindowWith: state, client: client)
} }
} }
@ -541,14 +391,14 @@ extension ctlInputMethod {
return false return false
}() }()
gCurrentCandidateController?.delegate = nil ctlCandidateCurrent?.delegate = nil
if useVerticalMode { if useVerticalMode {
gCurrentCandidateController = .vertical ctlCandidateCurrent = .vertical
} else if mgrPrefs.useHorizontalCandidateList { } else if mgrPrefs.useHorizontalCandidateList {
gCurrentCandidateController = .horizontal ctlCandidateCurrent = .horizontal
} else { } else {
gCurrentCandidateController = .vertical ctlCandidateCurrent = .vertical
} }
// set the attributes for the candidate panel (which uses NSAttributedString) // set the attributes for the candidate panel (which uses NSAttributedString)
@ -563,8 +413,11 @@ extension ctlInputMethod {
} }
func candidateFont(name: String?, size: CGFloat) -> NSFont { func candidateFont(name: String?, size: CGFloat) -> NSFont {
let currentMUIFont = (keyHandler.inputMode == InputMode.imeModeCHS) ? "Sarasa Term Slab SC" : "Sarasa Term Slab TC" let currentMUIFont =
var finalReturnFont = NSFont(name: currentMUIFont, size: size) ?? NSFont.systemFont(ofSize: size) (keyHandler.inputMode == InputMode.imeModeCHS)
? "Sarasa Term Slab SC" : "Sarasa Term Slab TC"
var finalReturnFont =
NSFont(name: currentMUIFont, size: size) ?? NSFont.systemFont(ofSize: size)
// macOS 11 Big Sur macOS 12 Monterey 使 // macOS 11 Big Sur macOS 12 Monterey 使
if #available(macOS 12.0, *) { finalReturnFont = NSFont.systemFont(ofSize: size) } if #available(macOS 12.0, *) { finalReturnFont = NSFont.systemFont(ofSize: size) }
if let name = name { if let name = name {
@ -573,21 +426,24 @@ extension ctlInputMethod {
return finalReturnFont return finalReturnFont
} }
gCurrentCandidateController?.keyLabelFont = labelFont(name: mgrPrefs.candidateKeyLabelFontName, size: keyLabelSize) ctlCandidateCurrent?.keyLabelFont = labelFont(
gCurrentCandidateController?.candidateFont = candidateFont(name: mgrPrefs.candidateTextFontName, size: textSize) name: mgrPrefs.candidateKeyLabelFontName, size: keyLabelSize)
ctlCandidateCurrent?.candidateFont = candidateFont(
name: mgrPrefs.candidateTextFontName, size: textSize)
let candidateKeys = mgrPrefs.candidateKeys let candidateKeys = mgrPrefs.candidateKeys
let keyLabels = candidateKeys.count > 4 ? Array(candidateKeys) : Array(mgrPrefs.defaultCandidateKeys) let keyLabels =
candidateKeys.count > 4 ? Array(candidateKeys) : Array(mgrPrefs.defaultCandidateKeys)
let keyLabelSuffix = state is InputState.AssociatedPhrases ? "^" : "" let keyLabelSuffix = state is InputState.AssociatedPhrases ? "^" : ""
gCurrentCandidateController?.keyLabels = keyLabels.map { ctlCandidateCurrent?.keyLabels = keyLabels.map {
CandidateKeyLabel(key: String($0), displayedText: String($0) + keyLabelSuffix) CandidateKeyLabel(key: String($0), displayedText: String($0) + keyLabelSuffix)
} }
gCurrentCandidateController?.delegate = self ctlCandidateCurrent?.delegate = self
gCurrentCandidateController?.reloadData() ctlCandidateCurrent?.reloadData()
currentCandidateClient = client currentCandidateClient = client
gCurrentCandidateController?.visible = true ctlCandidateCurrent?.visible = true
var lineHeightRect = NSMakeRect(0.0, 0.0, 16.0, 16.0) var lineHeightRect = NSMakeRect(0.0, 0.0, 16.0, 16.0)
var cursor: Int = 0 var cursor: Int = 0
@ -600,14 +456,22 @@ extension ctlInputMethod {
} }
while lineHeightRect.origin.x == 0 && lineHeightRect.origin.y == 0 && cursor >= 0 { while lineHeightRect.origin.x == 0 && lineHeightRect.origin.y == 0 && cursor >= 0 {
(client as? IMKTextInput)?.attributes(forCharacterIndex: cursor, lineHeightRectangle: &lineHeightRect) (client as? IMKTextInput)?.attributes(
forCharacterIndex: cursor, lineHeightRectangle: &lineHeightRect)
cursor -= 1 cursor -= 1
} }
if useVerticalMode { if useVerticalMode {
gCurrentCandidateController?.set(windowTopLeftPoint: NSMakePoint(lineHeightRect.origin.x + lineHeightRect.size.width + 4.0, lineHeightRect.origin.y - 4.0), bottomOutOfScreenAdjustmentHeight: lineHeightRect.size.height + 4.0) ctlCandidateCurrent?.set(
windowTopLeftPoint: NSMakePoint(
lineHeightRect.origin.x + lineHeightRect.size.width + 4.0,
lineHeightRect.origin.y - 4.0),
bottomOutOfScreenAdjustmentHeight: lineHeightRect.size.height + 4.0)
} else { } else {
gCurrentCandidateController?.set(windowTopLeftPoint: NSMakePoint(lineHeightRect.origin.x, lineHeightRect.origin.y - 4.0), bottomOutOfScreenAdjustmentHeight: lineHeightRect.size.height + 4.0) ctlCandidateCurrent?.set(
windowTopLeftPoint: NSMakePoint(
lineHeightRect.origin.x, lineHeightRect.origin.y - 4.0),
bottomOutOfScreenAdjustmentHeight: lineHeightRect.size.height + 4.0)
} }
} }
@ -618,7 +482,8 @@ extension ctlInputMethod {
cursor -= 1 cursor -= 1
} }
while lineHeightRect.origin.x == 0 && lineHeightRect.origin.y == 0 && cursor >= 0 { while lineHeightRect.origin.x == 0 && lineHeightRect.origin.y == 0 && cursor >= 0 {
(client as? IMKTextInput)?.attributes(forCharacterIndex: cursor, lineHeightRectangle: &lineHeightRect) (client as? IMKTextInput)?.attributes(
forCharacterIndex: cursor, lineHeightRectangle: &lineHeightRect)
cursor -= 1 cursor -= 1
} }
ctlInputMethod.tooltipController.show(tooltip: tooltip, at: lineHeightRect.origin) ctlInputMethod.tooltipController.show(tooltip: tooltip, at: lineHeightRect.origin)
@ -629,43 +494,50 @@ extension ctlInputMethod {
} }
} }
// MARK: -
@objc extension ctlInputMethod {
@objc static var areWeUsingOurOwnPhraseEditor: Bool = false
}
// MARK: - // MARK: -
extension ctlInputMethod: KeyHandlerDelegate { extension ctlInputMethod: KeyHandlerDelegate {
func candidateController(for keyHandler: KeyHandler) -> Any { func ctlCandidate(for keyHandler: KeyHandler) -> Any {
gCurrentCandidateController ?? .vertical ctlCandidateCurrent ?? .vertical
} }
func keyHandler(_ keyHandler: KeyHandler, didSelectCandidateAt index: Int, candidateController controller: Any) { func keyHandler(
if let controller = controller as? CandidateController { _ keyHandler: KeyHandler, didSelectCandidateAt index: Int,
self.candidateController(controller, didSelectCandidateAtIndex: UInt(index)) ctlCandidate controller: Any
) {
if let controller = controller as? ctlCandidate {
self.ctlCandidate(controller, didSelectCandidateAtIndex: UInt(index))
} }
} }
func keyHandler(_ keyHandler: KeyHandler, didRequestWriteUserPhraseWith state: InputState) -> Bool { func keyHandler(_ keyHandler: KeyHandler, didRequestWriteUserPhraseWith state: InputState)
-> Bool
{
guard let state = state as? InputState.Marking else { guard let state = state as? InputState.Marking else {
return false return false
} }
if !state.validToWrite { if !state.validToWrite {
return false return false
} }
let InputModeReversed: InputMode = (keyHandler.inputMode == InputMode.imeModeCHT) ? InputMode.imeModeCHS : InputMode.imeModeCHT let refInputModeReversed: InputMode =
mgrLangModel.writeUserPhrase(state.userPhrase, inputMode: keyHandler.inputMode, areWeDuplicating: state.chkIfUserPhraseExists, areWeDeleting: ctlInputMethod.areWeDeleting) (keyHandler.inputMode == InputMode.imeModeCHT)
mgrLangModel.writeUserPhrase(state.userPhraseConverted, inputMode: InputModeReversed, areWeDuplicating: false, areWeDeleting: ctlInputMethod.areWeDeleting) ? InputMode.imeModeCHS : InputMode.imeModeCHT
mgrLangModel.writeUserPhrase(
state.userPhrase, inputMode: keyHandler.inputMode,
areWeDuplicating: state.chkIfUserPhraseExists,
areWeDeleting: ctlInputMethod.areWeDeleting)
mgrLangModel.writeUserPhrase(
state.userPhraseConverted, inputMode: refInputModeReversed,
areWeDuplicating: false,
areWeDeleting: ctlInputMethod.areWeDeleting)
return true return true
} }
} }
// MARK: - // MARK: -
extension ctlInputMethod: CandidateControllerDelegate { extension ctlInputMethod: ctlCandidateDelegate {
func candidateCountForController(_ controller: CandidateController) -> UInt { func candidateCountForController(_ controller: ctlCandidate) -> UInt {
if let state = state as? InputState.ChoosingCandidate { if let state = state as? InputState.ChoosingCandidate {
return UInt(state.candidates.count) return UInt(state.candidates.count)
} else if let state = state as? InputState.AssociatedPhrases { } else if let state = state as? InputState.AssociatedPhrases {
@ -674,7 +546,9 @@ extension ctlInputMethod: CandidateControllerDelegate {
return 0 return 0
} }
func candidateController(_ controller: CandidateController, candidateAtIndex index: UInt) -> String { func ctlCandidate(_ controller: ctlCandidate, candidateAtIndex index: UInt)
-> String
{
if let state = state as? InputState.ChoosingCandidate { if let state = state as? InputState.ChoosingCandidate {
return state.candidates[Int(index)] return state.candidates[Int(index)]
} else if let state = state as? InputState.AssociatedPhrases { } else if let state = state as? InputState.AssociatedPhrases {
@ -683,13 +557,16 @@ extension ctlInputMethod: CandidateControllerDelegate {
return "" return ""
} }
func candidateController(_ controller: CandidateController, didSelectCandidateAtIndex index: UInt) { func ctlCandidate(_ controller: ctlCandidate, didSelectCandidateAtIndex index: UInt) {
let client = currentCandidateClient let client = currentCandidateClient
if let state = state as? InputState.SymbolTable, if let state = state as? InputState.SymbolTable,
let node = state.node.children?[Int(index)] { let node = state.node.children?[Int(index)]
{
if let children = node.children, !children.isEmpty { if let children = node.children, !children.isEmpty {
self.handle(state: .SymbolTable(node: node, useVerticalMode: state.useVerticalMode), client: currentCandidateClient) self.handle(
state: .SymbolTable(node: node, useVerticalMode: state.useVerticalMode),
client: currentCandidateClient)
} else { } else {
self.handle(state: .Committing(poppedText: node.title), client: client) self.handle(state: .Committing(poppedText: node.title), client: client)
self.handle(state: .Empty(), client: client) self.handle(state: .Empty(), client: client)
@ -710,7 +587,10 @@ extension ctlInputMethod: CandidateControllerDelegate {
let composingBuffer = inputting.composingBuffer let composingBuffer = inputting.composingBuffer
handle(state: .Committing(poppedText: composingBuffer), client: client) handle(state: .Committing(poppedText: composingBuffer), client: client)
if mgrPrefs.associatedPhrasesEnabled, if mgrPrefs.associatedPhrasesEnabled,
let associatePhrases = keyHandler.buildAssociatePhraseState(withKey: composingBuffer, useVerticalMode: state.useVerticalMode) as? InputState.AssociatedPhrases { let associatePhrases = keyHandler.buildAssociatePhraseState(
withKey: composingBuffer, useVerticalMode: state.useVerticalMode)
as? InputState.AssociatedPhrases
{
self.handle(state: associatePhrases, client: client) self.handle(state: associatePhrases, client: client)
} else { } else {
handle(state: .Empty(), client: client) handle(state: .Empty(), client: client)
@ -725,7 +605,10 @@ extension ctlInputMethod: CandidateControllerDelegate {
let selectedValue = state.candidates[Int(index)] let selectedValue = state.candidates[Int(index)]
handle(state: .Committing(poppedText: selectedValue), client: currentCandidateClient) handle(state: .Committing(poppedText: selectedValue), client: currentCandidateClient)
if mgrPrefs.associatedPhrasesEnabled, if mgrPrefs.associatedPhrasesEnabled,
let associatePhrases = keyHandler.buildAssociatePhraseState(withKey: selectedValue, useVerticalMode: state.useVerticalMode) as? InputState.AssociatedPhrases { let associatePhrases = keyHandler.buildAssociatePhraseState(
withKey: selectedValue, useVerticalMode: state.useVerticalMode)
as? InputState.AssociatedPhrases
{
self.handle(state: associatePhrases, client: client) self.handle(state: associatePhrases, client: client)
} else { } else {
handle(state: .Empty(), client: client) handle(state: .Empty(), client: client)
@ -733,4 +616,3 @@ extension ctlInputMethod: CandidateControllerDelegate {
} }
} }
} }

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
@ -149,32 +156,32 @@ struct ComposingBufferSize {
// 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"
} }
} }
@ -183,7 +190,8 @@ struct ComposingBufferSize {
// MARK: - // MARK: -
@objc public class mgrPrefs: NSObject { @objc public class mgrPrefs: NSObject {
static var allKeys: [String] { static var allKeys: [String] {
[kIsDebugModeEnabled, [
kIsDebugModeEnabled,
kUserDataFolderSpecified, kUserDataFolderSpecified,
kKeyboardLayoutPreference, kKeyboardLayoutPreference,
kBasisKeyboardLayoutPreference, kBasisKeyboardLayoutPreference,
@ -211,7 +219,8 @@ struct ComposingBufferSize {
kUseSCPCTypingMode, kUseSCPCTypingMode,
kMaxCandidateLength, kMaxCandidateLength,
kShouldNotFartInLieuOfBeep, kShouldNotFartInLieuOfBeep,
kAssociatedPhrasesEnabled] kAssociatedPhrasesEnabled,
]
} }
@objc public static func setMissingDefaults() { @objc public static func setMissingDefaults() {
@ -229,7 +238,9 @@ struct ComposingBufferSize {
// //
if UserDefaults.standard.object(forKey: kShowPageButtonsInCandidateWindow) == nil { if UserDefaults.standard.object(forKey: kShowPageButtonsInCandidateWindow) == nil {
UserDefaults.standard.set(mgrPrefs.showPageButtonsInCandidateWindow, forKey: kShowPageButtonsInCandidateWindow) UserDefaults.standard.set(
mgrPrefs.showPageButtonsInCandidateWindow, forKey: kShowPageButtonsInCandidateWindow
)
} }
// //
@ -239,27 +250,32 @@ struct ComposingBufferSize {
// 18 // 18
if UserDefaults.standard.object(forKey: kCandidateListTextSize) == nil { if UserDefaults.standard.object(forKey: kCandidateListTextSize) == nil {
UserDefaults.standard.set(mgrPrefs.candidateListTextSize, forKey: kCandidateListTextSize) UserDefaults.standard.set(
mgrPrefs.candidateListTextSize, forKey: kCandidateListTextSize)
} }
// true // true
if UserDefaults.standard.object(forKey: kChooseCandidateUsingSpace) == nil { if UserDefaults.standard.object(forKey: kChooseCandidateUsingSpace) == nil {
UserDefaults.standard.set(mgrPrefs.chooseCandidateUsingSpace, forKey: kChooseCandidateUsingSpace) UserDefaults.standard.set(
mgrPrefs.chooseCandidateUsingSpace, forKey: kChooseCandidateUsingSpace)
} }
// 使 // 使
if UserDefaults.standard.object(forKey: kShouldAutoReloadUserDataFiles) == nil { if UserDefaults.standard.object(forKey: kShouldAutoReloadUserDataFiles) == nil {
UserDefaults.standard.set(mgrPrefs.shouldAutoReloadUserDataFiles, forKey: kShouldAutoReloadUserDataFiles) UserDefaults.standard.set(
mgrPrefs.shouldAutoReloadUserDataFiles, forKey: kShouldAutoReloadUserDataFiles)
} }
// Tab // Tab
if UserDefaults.standard.object(forKey: kSpecifyTabKeyBehavior) == nil { if UserDefaults.standard.object(forKey: kSpecifyTabKeyBehavior) == nil {
UserDefaults.standard.set(mgrPrefs.specifyTabKeyBehavior, forKey: kSpecifyTabKeyBehavior) UserDefaults.standard.set(
mgrPrefs.specifyTabKeyBehavior, forKey: kSpecifyTabKeyBehavior)
} }
// Space // Space
if UserDefaults.standard.object(forKey: kSpecifySpaceKeyBehavior) == nil { if UserDefaults.standard.object(forKey: kSpecifySpaceKeyBehavior) == nil {
UserDefaults.standard.set(mgrPrefs.specifySpaceKeyBehavior, forKey: kSpecifySpaceKeyBehavior) UserDefaults.standard.set(
mgrPrefs.specifySpaceKeyBehavior, forKey: kSpecifySpaceKeyBehavior)
} }
// false // false
@ -269,22 +285,30 @@ struct ComposingBufferSize {
// false // false
if UserDefaults.standard.object(forKey: kAssociatedPhrasesEnabled) == nil { if UserDefaults.standard.object(forKey: kAssociatedPhrasesEnabled) == nil {
UserDefaults.standard.set(mgrPrefs.associatedPhrasesEnabled, forKey: kAssociatedPhrasesEnabled) UserDefaults.standard.set(
mgrPrefs.associatedPhrasesEnabled, forKey: kAssociatedPhrasesEnabled)
} }
// 0 // 0
if UserDefaults.standard.object(forKey: kSelectPhraseAfterCursorAsCandidatePreference) == nil { if UserDefaults.standard.object(forKey: kSelectPhraseAfterCursorAsCandidatePreference)
UserDefaults.standard.set(mgrPrefs.selectPhraseAfterCursorAsCandidate, forKey: kSelectPhraseAfterCursorAsCandidatePreference) == nil
{
UserDefaults.standard.set(
mgrPrefs.selectPhraseAfterCursorAsCandidate,
forKey: kSelectPhraseAfterCursorAsCandidatePreference)
} }
// //
if UserDefaults.standard.object(forKey: kMoveCursorAfterSelectingCandidate) == nil { if UserDefaults.standard.object(forKey: kMoveCursorAfterSelectingCandidate) == nil {
UserDefaults.standard.set(mgrPrefs.moveCursorAfterSelectingCandidate, forKey: kMoveCursorAfterSelectingCandidate) UserDefaults.standard.set(
mgrPrefs.moveCursorAfterSelectingCandidate,
forKey: kMoveCursorAfterSelectingCandidate)
} }
// //
if UserDefaults.standard.object(forKey: kUseHorizontalCandidateListPreference) == nil { if UserDefaults.standard.object(forKey: kUseHorizontalCandidateListPreference) == nil {
UserDefaults.standard.set(mgrPrefs.useHorizontalCandidateList, forKey: kUseHorizontalCandidateListPreference) UserDefaults.standard.set(
mgrPrefs.useHorizontalCandidateList, forKey: kUseHorizontalCandidateListPreference)
} }
// //
@ -294,22 +318,26 @@ struct ComposingBufferSize {
// //
if UserDefaults.standard.object(forKey: kChineseConversionEnabled) == nil { if UserDefaults.standard.object(forKey: kChineseConversionEnabled) == nil {
UserDefaults.standard.set(mgrPrefs.chineseConversionEnabled, forKey: kChineseConversionEnabled) UserDefaults.standard.set(
mgrPrefs.chineseConversionEnabled, forKey: kChineseConversionEnabled)
} }
// JIS // JIS
if UserDefaults.standard.object(forKey: kShiftJISShinjitaiOutputEnabled) == nil { if UserDefaults.standard.object(forKey: kShiftJISShinjitaiOutputEnabled) == nil {
UserDefaults.standard.set(mgrPrefs.shiftJISShinjitaiOutputEnabled, forKey: kShiftJISShinjitaiOutputEnabled) UserDefaults.standard.set(
mgrPrefs.shiftJISShinjitaiOutputEnabled, forKey: kShiftJISShinjitaiOutputEnabled)
} }
// //
if UserDefaults.standard.object(forKey: kPhraseReplacementEnabled) == nil { if UserDefaults.standard.object(forKey: kPhraseReplacementEnabled) == nil {
UserDefaults.standard.set(mgrPrefs.phraseReplacementEnabled, forKey: kPhraseReplacementEnabled) UserDefaults.standard.set(
mgrPrefs.phraseReplacementEnabled, forKey: kPhraseReplacementEnabled)
} }
// //
if UserDefaults.standard.object(forKey: kShouldNotFartInLieuOfBeep) == nil { if UserDefaults.standard.object(forKey: kShouldNotFartInLieuOfBeep) == nil {
UserDefaults.standard.set(mgrPrefs.shouldNotFartInLieuOfBeep, forKey: kShouldNotFartInLieuOfBeep) UserDefaults.standard.set(
mgrPrefs.shouldNotFartInLieuOfBeep, forKey: kShouldNotFartInLieuOfBeep)
} }
UserDefaults.standard.synchronize() UserDefaults.standard.synchronize()
@ -326,16 +354,17 @@ struct ComposingBufferSize {
} }
@UserDefault(key: kAppleLanguagesPreferences, defaultValue: []) @UserDefault(key: kAppleLanguagesPreferences, defaultValue: [])
@objc static var appleLanguages: Array<String> @objc static var appleLanguages: [String]
@UserDefault(key: kKeyboardLayoutPreference, defaultValue: 0) @UserDefault(key: kKeyboardLayoutPreference, defaultValue: 0)
@objc static var keyboardLayout: Int @objc static var keyboardLayout: Int
@objc static var keyboardLayoutName: String { @objc static var keyboardLayoutName: String {
(KeyboardLayout(rawValue: self.keyboardLayout) ?? KeyboardLayout.standard).name (KeyboardLayout(rawValue: self.keyboardLayout) ?? KeyboardLayout.ofStandard).name
} }
@UserDefault(key: kBasisKeyboardLayoutPreference, defaultValue: "com.apple.keylayout.ZhuyinBopomofo") @UserDefault(
key: kBasisKeyboardLayoutPreference, defaultValue: "com.apple.keylayout.ZhuyinBopomofo")
@objc static var basisKeyboardLayout: String @objc static var basisKeyboardLayout: String
@UserDefault(key: kShowPageButtonsInCandidateWindow, defaultValue: true) @UserDefault(key: kShowPageButtonsInCandidateWindow, defaultValue: true)
@ -411,7 +440,8 @@ struct ComposingBufferSize {
// JIS // JIS
if chineseConversionEnabled && shiftJISShinjitaiOutputEnabled { if chineseConversionEnabled && shiftJISShinjitaiOutputEnabled {
self.toggleShiftJISShinjitaiOutputEnabled() self.toggleShiftJISShinjitaiOutputEnabled()
UserDefaults.standard.set(shiftJISShinjitaiOutputEnabled, forKey: kShiftJISShinjitaiOutputEnabled) UserDefaults.standard.set(
shiftJISShinjitaiOutputEnabled, forKey: kShiftJISShinjitaiOutputEnabled)
} }
UserDefaults.standard.set(chineseConversionEnabled, forKey: kChineseConversionEnabled) UserDefaults.standard.set(chineseConversionEnabled, forKey: kChineseConversionEnabled)
return chineseConversionEnabled return chineseConversionEnabled
@ -423,8 +453,11 @@ struct ComposingBufferSize {
@objc @discardableResult static func toggleShiftJISShinjitaiOutputEnabled() -> Bool { @objc @discardableResult static func toggleShiftJISShinjitaiOutputEnabled() -> Bool {
shiftJISShinjitaiOutputEnabled = !shiftJISShinjitaiOutputEnabled shiftJISShinjitaiOutputEnabled = !shiftJISShinjitaiOutputEnabled
// JIS // JIS
if shiftJISShinjitaiOutputEnabled && chineseConversionEnabled {self.toggleChineseConversionEnabled()} if shiftJISShinjitaiOutputEnabled && chineseConversionEnabled {
UserDefaults.standard.set(shiftJISShinjitaiOutputEnabled, forKey: kShiftJISShinjitaiOutputEnabled) self.toggleChineseConversionEnabled()
}
UserDefaults.standard.set(
shiftJISShinjitaiOutputEnabled, forKey: kShiftJISShinjitaiOutputEnabled)
return shiftJISShinjitaiOutputEnabled return shiftJISShinjitaiOutputEnabled
} }
@ -439,7 +472,6 @@ struct ComposingBufferSize {
@UserDefault(key: kEscToCleanInputBuffer, defaultValue: true) @UserDefault(key: kEscToCleanInputBuffer, defaultValue: true)
@objc static var escToCleanInputBuffer: Bool @objc static var escToCleanInputBuffer: Bool
@UserDefault(key: kSpecifyTabKeyBehavior, defaultValue: false) @UserDefault(key: kSpecifyTabKeyBehavior, defaultValue: false)
@objc static var specifyTabKeyBehavior: Bool @objc static var specifyTabKeyBehavior: Bool
@ -499,13 +531,16 @@ struct ComposingBufferSize {
case .empty: case .empty:
return NSLocalizedString("Candidates keys cannot be empty.", comment: "") return NSLocalizedString("Candidates keys cannot be empty.", comment: "")
case .invalidCharacters: case .invalidCharacters:
return NSLocalizedString("Candidate keys can only contain ASCII characters like alphanumericals.", comment: "") return NSLocalizedString(
"Candidate keys can only contain ASCII characters like alphanumericals.",
comment: "")
case .containSpace: case .containSpace:
return NSLocalizedString("Candidate keys cannot contain space.", comment: "") return NSLocalizedString("Candidate keys cannot contain space.", comment: "")
case .duplicatedCharacters: case .duplicatedCharacters:
return NSLocalizedString("There should not be duplicated keys.", comment: "") return NSLocalizedString("There should not be duplicated keys.", comment: "")
case .tooShort: case .tooShort:
return NSLocalizedString("Please specify at least 4 candidate keys.", comment: "") return NSLocalizedString(
"Please specify at least 4 candidate keys.", comment: "")
case .tooLong: case .tooLong:
return NSLocalizedString("Maximum 15 candidate keys allowed.", comment: "") return NSLocalizedString("Maximum 15 candidate keys allowed.", comment: "")
} }

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,7 +65,8 @@ 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();
@ -128,7 +137,6 @@ public:
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.
/// ///
@ -137,8 +145,8 @@ protected:
/// @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;
@ -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()
{ {
@ -41,7 +49,8 @@ LMInstantiator::~LMInstantiator()
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);
} }
@ -54,7 +63,8 @@ bool LMInstantiator::isDataModelLoaded()
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);
} }
@ -67,7 +77,8 @@ bool LMInstantiator::isCNSDataLoaded()
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);
} }
@ -80,7 +91,8 @@ bool LMInstantiator::isMiscDataLoaded()
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,7 +128,8 @@ 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);
} }
@ -122,20 +137,23 @@ void LMInstantiator::loadUserAssociatedPhrases(const char *userAssociatedPhrases
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 = " ";
@ -155,14 +173,15 @@ const std::vector<Gramambular::Unigram> LMInstantiator::unigramsForKey(const std
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);
} }
@ -205,11 +229,13 @@ const std::vector<Gramambular::Unigram> LMInstantiator::unigramsForKey(const std
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;

View File

@ -1,31 +1,39 @@
// 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
{ {
@ -40,8 +48,11 @@ public:
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,7 +86,8 @@ 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;
} }
@ -84,13 +95,16 @@ bool AssociatedPhrases::open(const char *path)
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;
@ -111,9 +126,11 @@ const std::vector<std::string> AssociatedPhrases::valuesForKey(const std::string
{ {
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; const std::vector<Row> &rows = iter->second;
for (const auto& row : rows) { 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()});
} }

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,9 +40,11 @@ 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();
@ -58,7 +67,8 @@ protected:
} }
}; };
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,9 +321,11 @@ 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; const vector<Row> &r = (*i).second;
for (vector<Row>::const_iterator ri = r.begin(), re = r.end(); ri != re; ++ri) { for (vector<Row>::const_iterator ri = r.begin(), re = r.end(); ri != re; ++ri)
{
const Row &row = *ri; const Row &row = *ri;
cerr << row.key << " " << row.value << " " << row.logProbability << "\n"; cerr << row.key << " " << row.value << " " << row.logProbability << "\n";
rows++; rows++;
@ -303,8 +343,10 @@ const std::vector<Gramambular::Unigram> vChewing::CoreLM::unigramsForKey(const s
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;

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 class CNSLM : public UserPhrasesLM
{ {
public: public:
virtual bool allowConsolidation() override { 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 class SymbolLM : public UserPhrasesLM
{ {
public: public:
virtual bool allowConsolidation() override { 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 class UserSymbolLM : public UserPhrasesLM
{ {
public: public:
virtual bool allowConsolidation() override { 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,11 +33,15 @@ 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;
@ -38,17 +49,20 @@ bool vChewing::ParselessLM::isLoaded()
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);
@ -138,7 +159,8 @@ vChewing::ParselessLM::unigramsForKey(const std::string& key)
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,9 +34,11 @@ 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;
@ -37,10 +46,9 @@ public:
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::vector<Gramambular::Unigram> unigramsForKey(
const std::string &key) override; const std::string &key) override;
const std::vector<Gramambular::Unigram> unigramsForKey(const std::string &key) override;
bool hasUnigramsForKey(const std::string &key) override; bool hasUnigramsForKey(const std::string &key) override;
private: private:

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 *mid = top + (bottom - top) / 2;
const char *ptr = mid; 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,17 +31,18 @@ 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: public:
ParselessPhraseDB( ParselessPhraseDB(const char *buf, size_t length);
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)

View File

@ -1,30 +1,38 @@
// 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
{ {
@ -43,6 +51,6 @@ protected:
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,7 +80,8 @@ 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;
} }
@ -79,13 +89,16 @@ bool PhraseReplacementMap::open(const char *path)
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;
@ -105,12 +119,12 @@ void PhraseReplacementMap::close()
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,50 +30,48 @@ 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,
double eventTimestamp,
double timestamp,
double lambda);
static bool IsEndingPunctuation(const std::string &value); static bool IsEndingPunctuation(const std::string &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);
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);
@ -76,12 +81,13 @@ void UserOverrideModel::observe(const std::vector<Gramambular::NodeAnchor>& walk
} }
} }
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();
} }
@ -91,20 +97,17 @@ std::string UserOverrideModel::suggest(const std::vector<Gramambular::NodeAnchor
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, double overrideScore = Score(o.count, observation.count, o.timestamp, timestamp, m_decayExponent);
observation.count, if (overrideScore == 0.0)
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,36 +32,40 @@ 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);
}; };
@ -69,4 +80,3 @@ private:
}; // namespace vChewing }; // namespace vChewing
#endif #endif

View File

@ -1,31 +1,39 @@
// 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
{ {
@ -38,21 +46,27 @@ 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,
const std::string &key);
virtual const std::vector<Gramambular::Unigram> unigramsForKey(const std::string &key); virtual const std::vector<Gramambular::Unigram> unigramsForKey(const std::string &key);
virtual bool hasUnigramsForKey(const std::string &key); virtual bool hasUnigramsForKey(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;
}; };
@ -63,6 +77,6 @@ protected:
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,7 +90,8 @@ 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;
} }
@ -87,14 +99,18 @@ bool UserPhrasesLM::open(const char *path)
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,15 +130,18 @@ 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; const std::vector<Row> &rows = entry.second;
for (const auto& row : rows) { 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>();
} }
@ -130,9 +150,11 @@ const std::vector<Gramambular::Unigram> UserPhrasesLM::unigramsForKey(const std:
{ {
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; const std::vector<Row> &rows = iter->second;
for (const auto& row : rows) { 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;

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,61 +61,79 @@ 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]);
} }
} }
@ -116,8 +141,10 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing
+ (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,7 +398,8 @@ 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];
} }

View File

@ -1,25 +1,32 @@
// 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

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,8 +31,10 @@ 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 { {
class Bigram
{
public: public:
Bigram(); Bigram();
@ -37,26 +46,27 @@ public:
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 << ",";
} }
} }
@ -65,20 +75,29 @@ inline std::ostream& operator<<(std::ostream& 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;
@ -88,5 +107,4 @@ inline bool Bigram::operator<(const Bigram& another) const {
} }
} // 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,9 +33,11 @@ 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: public:
explicit BlockReadingBuilder(LanguageModel *lm); explicit BlockReadingBuilder(LanguageModel *lm);
void clear(); void clear();
@ -53,8 +62,7 @@ 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;
@ -67,25 +75,34 @@ protected:
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);
@ -93,42 +110,49 @@ inline void BlockReadingBuilder::insertReadingAtCursor(
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);
@ -139,44 +163,56 @@ inline bool BlockReadingBuilder::removeHeadReadings(size_t count) {
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,15 +221,17 @@ 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;
} }
} }
@ -201,5 +239,4 @@ inline const std::string BlockReadingBuilder::Join(
} }
} // 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,15 +34,15 @@ 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);
@ -49,46 +56,52 @@ public:
// 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]; Span &span = m_spans[p];
for (size_t ni = 0; ni <= span.maximumLength(); ni++) { for (size_t ni = 0; ni <= span.maximumLength(); ni++)
{
Node *np = span.nodeOfLength(ni); Node *np = span.nodeOfLength(ni);
if (np) { if (np)
if (!p) { {
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]; Span &dstSpan = m_spans[p + ni];
for (size_t q = 0; q <= dstSpan.maximumLength(); q++) { for (size_t q = 0; q <= dstSpan.maximumLength(); q++)
{
Node *dn = dstSpan.nodeOfLength(q); Node *dn = dstSpan.nodeOfLength(q);
if (dn) { if (dn)
sst << np->currentKeyValue().value << " -> " {
<< dn->currentKeyValue().value << ";" << std::endl; 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;
} }
@ -105,14 +118,19 @@ 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());
} }
} }
@ -120,55 +138,74 @@ inline void Grid::insertNode(const Node& node, size_t location,
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++) { {
for (size_t i = 0; i < location; i++)
{
Span &span = m_spans[i]; Span &span = m_spans[i];
if (i + span.maximumLength() >= location) { if (i + span.maximumLength() >= location)
{
Node *np = span.nodeOfLength(location - i); Node *np = span.nodeOfLength(location - i);
if (np) { if (np)
{
NodeAnchor na; NodeAnchor na;
na.node = np; na.node = np;
na.location = i; na.location = i;
@ -183,21 +220,28 @@ inline std::vector<NodeAnchor> Grid::nodesEndingAt(size_t location) {
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++) { {
for (size_t i = 0; i < location; i++)
{
Span &span = m_spans[i]; Span &span = m_spans[i];
if (i + span.maximumLength() >= location) { if (i + span.maximumLength() >= location)
for (size_t j = 1, m = span.maximumLength(); j <= m; j++) { {
if (i + j < 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;
@ -215,18 +259,21 @@ inline std::vector<NodeAnchor> Grid::nodesCrossingOrEndingAt(size_t location) {
// 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) { {
if (candidates[i].value == value)
{
const_cast<Node *>(nodeAnchor.node)->selectCandidateAtIndex(i); const_cast<Node *>(nodeAnchor.node)->selectCandidateAtIndex(i);
node = nodeAnchor; node = nodeAnchor;
break; break;
@ -236,19 +283,22 @@ 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;
} }
} }
@ -257,5 +307,4 @@ inline void Grid::overrideNodeScoreForSelectedCandidate(
} // 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,9 +30,11 @@ 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;
@ -34,25 +43,29 @@ public:
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: public:
virtual ~LanguageModel() {} virtual ~LanguageModel()
{
}
virtual const std::vector<Bigram> bigramsForKeys( virtual const std::vector<Bigram> bigramsForKeys(const std::string &preceedingKey, const std::string &key) = 0;
const std::string& preceedingKey, const std::string& key) = 0;
virtual const std::vector<Unigram> unigramsForKey(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 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,16 +34,16 @@ 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( void primeNodeWithPreceedingKeyValues(const std::vector<KeyValuePair> &keyValues);
const std::vector<KeyValuePair>& keyValues);
bool isCandidateFixed() const; bool isCandidateFixed() const;
const std::vector<KeyValuePair> &candidates() const; const std::vector<KeyValuePair> &candidates() const;
@ -67,65 +74,65 @@ protected:
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(); for (std::vector<Bigram>::const_iterator bi = bigrams.begin(); bi != bigrams.end(); ++bi)
bi != bigrams.end(); ++bi) { {
const Bigram &bigram = *bi; const Bigram &bigram = *bi;
if (bigram.score > max) { 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;
} }
@ -135,25 +142,35 @@ 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;
} }
@ -161,53 +178,72 @@ inline void Node::selectCandidateAtIndex(size_t index, bool 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,33 +31,39 @@ 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 << "<-";
} }
} }
@ -59,5 +72,4 @@ inline std::ostream& operator<<(std::ostream& 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,8 +33,10 @@ 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 { {
class Span
{
public: public:
void clear(); void clear();
void insertNodeOfLength(const Node &node, size_t length); void insertNodeOfLength(const Node &node, size_t length);
@ -41,52 +50,63 @@ protected:
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
{
return m_maximumLength;
}
} // 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 UNIGRAM_H_ #ifndef UNIGRAM_H_
@ -24,9 +31,11 @@ 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();
@ -39,7 +48,8 @@ public:
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,17 +57,18 @@ 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 << ",";
} }
} }
@ -66,25 +77,32 @@ inline std::ostream& operator<<(std::ostream& 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,23 +32,27 @@ 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: public:
explicit Walker(Grid *inGrid); explicit Walker(Grid *inGrid);
const std::vector<NodeAnchor> reverseWalk(size_t location, const std::vector<NodeAnchor> reverseWalk(size_t location, double accumulatedScore = 0.0);
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>();
} }
@ -49,29 +60,31 @@ inline const std::vector<NodeAnchor> Walker::reverseWalk(
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;
} }
} }
@ -80,5 +93,4 @@ inline const std::vector<NodeAnchor> Walker::reverseWalk(
} }
} // namespace Gramambular } // namespace Gramambular
#endif #endif

View File

@ -1,20 +1,26 @@
// 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

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
@ -34,7 +41,7 @@ if CommandLine.arguments.count > 1 {
} }
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)
} }
@ -44,7 +51,9 @@ if !loaded {
exit(-1) exit(-1)
} }
guard let bundleID = Bundle.main.bundleIdentifier, let server = IMKServer(name: kConnectionName, bundleIdentifier: bundleID) else { guard let bundleID = Bundle.main.bundleIdentifier,
let server = IMKServer(name: kConnectionName, bundleIdentifier: bundleID)
else {
NSLog("Fatal error: Cannot initialize input method server with connection \(kConnectionName).") NSLog("Fatal error: Cannot initialize input method server with connection \(kConnectionName).")
exit(-1) exit(-1)
} }

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,20 +1,26 @@
// 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
@ -45,12 +51,13 @@ public class NotifierController: NSWindowController, NotifierWindowDelegate {
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(
with: NSSize(width: width, height: 1600), options: .usesLineFragmentOrigin)
let height = rect.height let height = rect.height
let x = messageTextField.frame.origin.x let x = messageTextField.frame.origin.x
let y = ((window?.frame.height ?? kWindowHeight) - height) / 2 let y = ((window?.frame.height ?? kWindowHeight) - height) / 2
@ -105,7 +112,8 @@ public class NotifierController: NSWindowController, NotifierWindowDelegate {
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(
contentRect: windowRect, styleMask: styleMask, backing: .buffered, defer: false)
panel.contentView = transparentVisualEffect panel.contentView = transparentVisualEffect
panel.isMovableByWindowBackground = true panel.isMovableByWindowBackground = true
panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel)) panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel))
@ -170,7 +178,10 @@ public class NotifierController: NSWindowController, NotifierWindowDelegate {
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) {
@ -186,7 +197,9 @@ public class NotifierController: NSWindowController, NotifierWindowDelegate {
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() {

View File

@ -1,20 +1,26 @@
// 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
@ -33,7 +39,8 @@ public class TooltipController: NSWindowController {
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(
contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false)
panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel) + 1) panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel) + 1)
panel.hasShadow = true panel.hasShadow = true
@ -76,10 +83,9 @@ public class TooltipController: NSWindowController {
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
} }
@ -112,8 +118,9 @@ public class TooltipController: NSWindowController {
} }
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(
with: NSSize(width: 1600.0, height: 1600.0), options: .usesLineFragmentOrigin)
rect.size.width += 10 rect.size.width += 10
messageTextField.frame = rect messageTextField.frame = rect
window?.setFrame(rect, display: true) window?.setFrame(rect, display: 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
@ -30,17 +37,23 @@ import Cocoa
window?.standardWindowButton(.closeButton)?.isHidden = true window?.standardWindowButton(.closeButton)?.isHidden = true
window?.standardWindowButton(.miniaturizeButton)?.isHidden = true window?.standardWindowButton(.miniaturizeButton)?.isHidden = true
window?.standardWindowButton(.zoomButton)?.isHidden = true window?.standardWindowButton(.zoomButton)?.isHidden = true
guard let installingVersion = Bundle.main.infoDictionary?[kCFBundleVersionKey as String] as? String, guard
let versionString = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String else { let installingVersion = Bundle.main.infoDictionary?[kCFBundleVersionKey as String]
as? String,
let versionString = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
else {
return return
} }
if let copyrightLabel = Bundle.main.localizedInfoDictionary?["NSHumanReadableCopyright"] as? String { if let copyrightLabel = Bundle.main.localizedInfoDictionary?["NSHumanReadableCopyright"]
as? String
{
appCopyrightLabel.stringValue = copyrightLabel appCopyrightLabel.stringValue = copyrightLabel
} }
if let eulaContent = Bundle.main.localizedInfoDictionary?["CFEULAContent"] as? String { if let eulaContent = Bundle.main.localizedInfoDictionary?["CFEULAContent"] as? String {
appEULAContent.string = eulaContent appEULAContent.string = eulaContent
} }
appVersionLabel.stringValue = String(format: "%@ Build %@", versionString, installingVersion) appVersionLabel.stringValue = String(
format: "%@ Build %@", versionString, installingVersion)
} }
@IBAction func btnWiki(_ sender: NSButton) { @IBAction func btnWiki(_ sender: NSButton) {

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
@ -34,7 +41,10 @@ class ctlNonModalAlertWindow: NSWindowController {
@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(
title: String, content: String, confirmButtonTitle: String, cancelButtonTitle: String?,
cancelAsDefault: Bool, delegate: ctlNonModalAlertWindowDelegate?
) {
if window?.isVisible == true { if window?.isVisible == true {
self.delegate?.ctlNonModalAlertWindowDidCancel(self) self.delegate?.ctlNonModalAlertWindowDidCancel(self)
} }
@ -84,7 +94,9 @@ class ctlNonModalAlertWindow: NSWindowController {
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(
with: infiniteHeightFrame.size, options: [.usesLineFragmentOrigin],
attributes: [.font: contentTextField.font!])
newFrame.size.width = max(newFrame.size.width, oldFrame.size.width) newFrame.size.width = max(newFrame.size.width, oldFrame.size.width)
newFrame.size.height += 4.0 newFrame.size.height += 4.0
newFrame.origin = oldFrame.origin newFrame.origin = oldFrame.origin

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 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 {
@ -45,7 +52,8 @@ extension RangeReplaceableCollection where Element: Hashable {
override func windowDidLoad() { override func windowDidLoad() {
super.windowDidLoad() super.windowDidLoad()
lblCurrentlySpecifiedUserDataFolder.placeholderString = mgrLangModel.dataFolderPath(isDefaultFolder: true) lblCurrentlySpecifiedUserDataFolder.placeholderString = mgrLangModel.dataFolderPath(
isDefaultFolder: true)
let languages = ["auto", "en", "zh-Hans", "zh-Hant", "ja"] let languages = ["auto", "en", "zh-Hans", "zh-Hant", "ja"]
var autoMUISelectItem: NSMenuItem? = nil var autoMUISelectItem: NSMenuItem? = nil
@ -79,15 +87,18 @@ extension RangeReplaceableCollection where Element: Hashable {
basisKeyboardLayoutButton.menu?.removeAllItems() basisKeyboardLayoutButton.menu?.removeAllItems()
let menuItem_AppleZhuyinBopomofo = NSMenuItem() let itmAppleZhuyinBopomofo = NSMenuItem()
menuItem_AppleZhuyinBopomofo.title = String(format: NSLocalizedString("Apple Zhuyin Bopomofo", comment: "")) itmAppleZhuyinBopomofo.title = String(
menuItem_AppleZhuyinBopomofo.representedObject = String("com.apple.keylayout.ZhuyinBopomofo") format: NSLocalizedString("Apple Zhuyin Bopomofo", comment: ""))
basisKeyboardLayoutButton.menu?.addItem(menuItem_AppleZhuyinBopomofo) itmAppleZhuyinBopomofo.representedObject = String(
"com.apple.keylayout.ZhuyinBopomofo")
basisKeyboardLayoutButton.menu?.addItem(itmAppleZhuyinBopomofo)
let menuItem_AppleZhuyinEten = NSMenuItem() let itmAppleZhuyinEten = NSMenuItem()
menuItem_AppleZhuyinEten.title = String(format: NSLocalizedString("Apple Zhuyin Eten", comment: "")) itmAppleZhuyinEten.title = String(
menuItem_AppleZhuyinEten.representedObject = String("com.apple.keylayout.ZhuyinEten") format: NSLocalizedString("Apple Zhuyin Eten", comment: ""))
basisKeyboardLayoutButton.menu?.addItem(menuItem_AppleZhuyinEten) itmAppleZhuyinEten.representedObject = String("com.apple.keylayout.ZhuyinEten")
basisKeyboardLayoutButton.menu?.addItem(itmAppleZhuyinEten)
let basisKeyboardLayoutID = mgrPrefs.basisKeyboardLayout let basisKeyboardLayoutID = mgrPrefs.basisKeyboardLayout
@ -101,8 +112,11 @@ extension RangeReplaceableCollection where Element: Hashable {
continue continue
} }
if let asciiCapablePtr = TISGetInputSourceProperty(source, kTISPropertyInputSourceIsASCIICapable) { if let asciiCapablePtr = TISGetInputSourceProperty(
let asciiCapable = Unmanaged<CFBoolean>.fromOpaque(asciiCapablePtr).takeUnretainedValue() source, kTISPropertyInputSourceIsASCIICapable)
{
let asciiCapable = Unmanaged<CFBoolean>.fromOpaque(asciiCapablePtr)
.takeUnretainedValue()
if asciiCapable != kCFBooleanTrue { if asciiCapable != kCFBooleanTrue {
continue continue
} }
@ -120,12 +134,14 @@ extension RangeReplaceableCollection where Element: Hashable {
} }
guard let sourceIDPtr = TISGetInputSourceProperty(source, kTISPropertyInputSourceID), guard let sourceIDPtr = TISGetInputSourceProperty(source, kTISPropertyInputSourceID),
let localizedNamePtr = TISGetInputSourceProperty(source, kTISPropertyLocalizedName) else { let localizedNamePtr = TISGetInputSourceProperty(source, kTISPropertyLocalizedName)
else {
continue continue
} }
let sourceID = String(Unmanaged<CFString>.fromOpaque(sourceIDPtr).takeUnretainedValue()) let sourceID = String(Unmanaged<CFString>.fromOpaque(sourceIDPtr).takeUnretainedValue())
let localizedName = String(Unmanaged<CFString>.fromOpaque(localizedNamePtr).takeUnretainedValue()) let localizedName = String(
Unmanaged<CFString>.fromOpaque(localizedNamePtr).takeUnretainedValue())
let menuItem = NSMenuItem() let menuItem = NSMenuItem()
menuItem.title = localizedName menuItem.title = localizedName
@ -142,9 +158,9 @@ extension RangeReplaceableCollection where Element: Hashable {
switch basisKeyboardLayoutID { switch basisKeyboardLayoutID {
case "com.apple.keylayout.ZhuyinBopomofo": case "com.apple.keylayout.ZhuyinBopomofo":
chosenBaseKeyboardLayoutItem = menuItem_AppleZhuyinBopomofo chosenBaseKeyboardLayoutItem = itmAppleZhuyinBopomofo
case "com.apple.keylayout.ZhuyinEten": case "com.apple.keylayout.ZhuyinEten":
chosenBaseKeyboardLayoutItem = menuItem_AppleZhuyinEten chosenBaseKeyboardLayoutItem = itmAppleZhuyinEten
default: default:
break // nothing to do break // nothing to do
} }
@ -198,10 +214,9 @@ extension RangeReplaceableCollection where Element: Hashable {
} }
} }
if let language = uiLanguageButton.selectedItem?.representedObject as? String { if let language = uiLanguageButton.selectedItem?.representedObject as? String {
if (language != "auto") { if language != "auto" {
mgrPrefs.appleLanguages = [language] mgrPrefs.appleLanguages = [language]
} } else {
else {
UserDefaults.standard.removeObject(forKey: "AppleLanguages") UserDefaults.standard.removeObject(forKey: "AppleLanguages")
} }
@ -215,18 +230,21 @@ extension RangeReplaceableCollection where Element: Hashable {
} }
@IBAction func changeSelectionKeyAction(_ sender: Any) { @IBAction func changeSelectionKeyAction(_ sender: Any) {
guard let keys = (sender as AnyObject).stringValue?.trimmingCharacters(in: .whitespacesAndNewlines).charDeDuplicate else { guard
let keys = (sender as AnyObject).stringValue?.trimmingCharacters(
in: .whitespacesAndNewlines
)
.charDeDuplicate
else {
return return
} }
do { do {
try mgrPrefs.validate(candidateKeys: keys) try mgrPrefs.validate(candidateKeys: keys)
mgrPrefs.candidateKeys = keys mgrPrefs.candidateKeys = keys
selectionKeyComboBox.stringValue = mgrPrefs.candidateKeys selectionKeyComboBox.stringValue = mgrPrefs.candidateKeys
} } catch mgrPrefs.CandidateKeyError.empty {
catch mgrPrefs.CandidateKeyError.empty {
selectionKeyComboBox.stringValue = mgrPrefs.candidateKeys selectionKeyComboBox.stringValue = mgrPrefs.candidateKeys
} } catch {
catch {
if let window = window { if let window = window {
let alert = NSAlert(error: error) let alert = NSAlert(error: error)
alert.beginSheetModal(for: window) { response in alert.beginSheetModal(for: window) { response in
@ -243,31 +261,35 @@ extension RangeReplaceableCollection where Element: Hashable {
} }
@IBAction func chooseUserDataFolderToSpecify(_ sender: Any) { @IBAction func chooseUserDataFolderToSpecify(_ sender: Any) {
IME.dlgOpenPath.title = NSLocalizedString("Choose your desired user data folder.", comment: ""); IME.dlgOpenPath.title = NSLocalizedString(
IME.dlgOpenPath.showsResizeIndicator = true; "Choose your desired user data folder.", comment: "")
IME.dlgOpenPath.showsHiddenFiles = true; IME.dlgOpenPath.showsResizeIndicator = true
IME.dlgOpenPath.canChooseFiles = false; IME.dlgOpenPath.showsHiddenFiles = true
IME.dlgOpenPath.canChooseDirectories = true; IME.dlgOpenPath.canChooseFiles = false
IME.dlgOpenPath.canChooseDirectories = true
let PreviousFolderValidity = mgrLangModel.checkIfSpecifiedUserDataFolderValid(NSString(string: mgrPrefs.userDataFolderSpecified).expandingTildeInPath) let bolPreviousFolderValidity = mgrLangModel.checkIfSpecifiedUserDataFolderValid(
NSString(string: mgrPrefs.userDataFolderSpecified).expandingTildeInPath)
if self.window != nil { if self.window != nil {
IME.dlgOpenPath.beginSheetModal(for: self.window!) { result in IME.dlgOpenPath.beginSheetModal(for: self.window!) { result in
if result == NSApplication.ModalResponse.OK { if result == NSApplication.ModalResponse.OK {
if (IME.dlgOpenPath.url != nil) { if IME.dlgOpenPath.url != nil {
if (mgrLangModel.checkIfSpecifiedUserDataFolderValid(IME.dlgOpenPath.url!.path)) { if mgrLangModel.checkIfSpecifiedUserDataFolderValid(
IME.dlgOpenPath.url!.path)
{
mgrPrefs.userDataFolderSpecified = IME.dlgOpenPath.url!.path mgrPrefs.userDataFolderSpecified = IME.dlgOpenPath.url!.path
IME.initLangModels(userOnly: true) IME.initLangModels(userOnly: true)
} else { } else {
clsSFX.beep() clsSFX.beep()
if !PreviousFolderValidity { if !bolPreviousFolderValidity {
self.resetSpecifiedUserDataFolder(self) self.resetSpecifiedUserDataFolder(self)
} }
return return
} }
} }
} else { } else {
if !PreviousFolderValidity { if !bolPreviousFolderValidity {
self.resetSpecifiedUserDataFolder(self) self.resetSpecifiedUserDataFolder(self)
} }
return return

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
@ -36,7 +42,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
} }
// New About Window // New About Window
@objc func showAbout() { @objc func showAbout() {
if (ctlAboutWindowInstance == nil) { if ctlAboutWindowInstance == nil {
ctlAboutWindowInstance = ctlAboutWindow.init(windowNibName: "frmAboutWindow") ctlAboutWindowInstance = ctlAboutWindow.init(windowNibName: "frmAboutWindow")
} }
ctlAboutWindowInstance?.window?.center() ctlAboutWindowInstance?.window?.center()

View File

@ -1,23 +1,29 @@
// 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 = ""

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
@ -36,7 +42,9 @@ class Document: NSDocument {
} }
// 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(
to url: URL, ofType typeName: String, for saveOperation: NSDocument.SaveOperationType
) -> Bool {
return true return true
} }
@ -53,7 +61,9 @@ class Document: NSDocument {
let storyboard = NSStoryboard(name: NSStoryboard.Name("Main"), bundle: nil) let storyboard = NSStoryboard(name: NSStoryboard.Name("Main"), bundle: nil)
if let windowController = if let windowController =
storyboard.instantiateController( storyboard.instantiateController(
withIdentifier: NSStoryboard.SceneIdentifier("Document Window Controller")) as? NSWindowController { withIdentifier: NSStoryboard.SceneIdentifier("Document Window Controller"))
as? NSWindowController
{
addWindowController(windowController) addWindowController(windowController)
// Set the view controller's represented object as your document. // Set the view controller's represented object as your document.
@ -69,7 +79,7 @@ class Document: NSDocument {
/// - Tag: readExample /// - Tag: readExample
override func read(from data: Data, ofType typeName: String) throws { override func read(from data: Data, ofType typeName: String) throws {
var strToDealWith = String(decoding: data, as: UTF8.self) var strToDealWith = String(decoding: data, as: UTF8.self)
strToDealWith.formatConsolidate(HYPY2BPMF: false) strToDealWith.formatConsolidate(cnvHYPYtoBPMF: false)
let processedIncomingData = Data(strToDealWith.utf8) let processedIncomingData = Data(strToDealWith.utf8)
content.read(from: processedIncomingData) content.read(from: processedIncomingData)
} }
@ -77,7 +87,7 @@ class Document: NSDocument {
/// - Tag: writeExample /// - Tag: writeExample
override func data(ofType typeName: String) throws -> Data { override func data(ofType typeName: String) throws -> Data {
var strToDealWith = content.contentString var strToDealWith = content.contentString
strToDealWith.formatConsolidate(HYPY2BPMF: true) strToDealWith.formatConsolidate(cnvHYPYtoBPMF: true)
let outputData = Data(strToDealWith.utf8) let outputData = Data(strToDealWith.utf8)
return outputData return outputData
} }
@ -96,7 +106,8 @@ class Document: NSDocument {
thePrintInfo.topMargin = 72.0 thePrintInfo.topMargin = 72.0
thePrintInfo.bottomMargin = 72.0 thePrintInfo.bottomMargin = 72.0
printInfo.dictionary().setObject(NSNumber(value: true), printInfo.dictionary().setObject(
NSNumber(value: true),
forKey: NSPrintInfo.AttributeKey.headerAndFooter as NSCopying) forKey: NSPrintInfo.AttributeKey.headerAndFooter as NSCopying)
return thePrintInfo return thePrintInfo
@ -104,7 +115,8 @@ class Document: NSDocument {
@objc @objc
func printOperationDidRun( func printOperationDidRun(
_ printOperation: NSPrintOperation, success: Bool, contextInfo: UnsafeMutableRawPointer?) { _ printOperation: NSPrintOperation, success: Bool, contextInfo: UnsafeMutableRawPointer?
) {
// Printing finished... // Printing finished...
} }
@ -112,8 +124,10 @@ class Document: NSDocument {
// Print the NSTextView. // Print the NSTextView.
// Create a copy to manipulate for printing. // Create a copy to manipulate for printing.
let pageSize = NSSize(width: (printInfo.paperSize.width), height: (printInfo.paperSize.height)) let pageSize = NSSize(
let textView = NSTextView(frame: NSRect(x: 0.0, y: 0.0, width: pageSize.width, height: pageSize.height)) width: (printInfo.paperSize.width), height: (printInfo.paperSize.height))
let textView = NSTextView(
frame: NSRect(x: 0.0, y: 0.0, width: pageSize.width, height: pageSize.height))
// Make sure we print on a white background. // Make sure we print on a white background.
textView.appearance = NSAppearance(named: .aqua) textView.appearance = NSAppearance(named: .aqua)

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 Foundation import Foundation
@ -22,15 +28,17 @@ extension String {
mutating func regReplace(pattern: String, replaceWith: String = "") { 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(
pattern: pattern, options: [.caseInsensitive, .anchorsMatchLines])
let range = NSRange(self.startIndex..., in: self) let range = NSRange(self.startIndex..., in: self)
self = regex.stringByReplacingMatches(in: self, options: [], range: range, withTemplate: replaceWith) self = regex.stringByReplacingMatches(
in: self, options: [], range: range, withTemplate: replaceWith)
} catch { return } } catch { return }
} }
mutating func selfReplace(_ strOf: String, _ strWith: String = "") { mutating func selfReplace(_ strOf: String, _ strWith: String = "") {
self = self.replacingOccurrences(of: strOf, with: strWith) self = self.replacingOccurrences(of: strOf, with: strWith)
} }
mutating func formatConsolidate(HYPY2BPMF: Bool) { mutating func formatConsolidate(cnvHYPYtoBPMF: Bool) {
// Step 1: Consolidating formats per line. // Step 1: Consolidating formats per line.
var strProcessed = self var strProcessed = self
// //
@ -41,7 +49,7 @@ extension String {
// ASCII // ASCII
strProcessed.regReplace(pattern: #"( +| +| +|\t+)+"#, replaceWith: " ") strProcessed.regReplace(pattern: #"( +| +| +|\t+)+"#, replaceWith: " ")
strProcessed.regReplace(pattern: #"(^ | $)"#, replaceWith: "") // strProcessed.regReplace(pattern: #"(^ | $)"#, replaceWith: "") //
strProcessed.regReplace(pattern: #"(\f+|\r+|\n+)+"#, replaceWith: "\n") // CR & Form Feed to LF, strProcessed.regReplace(pattern: #"(\f+|\r+|\n+)+"#, replaceWith: "\n") // CR & FF to LF,
if strProcessed.prefix(1) == " " { // if strProcessed.prefix(1) == " " { //
strProcessed.removeFirst() strProcessed.removeFirst()
} }
@ -49,7 +57,7 @@ extension String {
strProcessed.removeLast() strProcessed.removeLast()
} }
var arrData = [""] var arrData = [""]
if HYPY2BPMF { if cnvHYPYtoBPMF {
// Step 0: Convert HanyuPinyin to Bopomofo. // Step 0: Convert HanyuPinyin to Bopomofo.
arrData = strProcessed.components(separatedBy: "\n") arrData = strProcessed.components(separatedBy: "\n")
strProcessed = "" // Reset its value strProcessed = "" // Reset its value
@ -492,8 +500,8 @@ extension String {
} }
} }
// Step 3: Add Formatted Pragma // Step 3: Add Formatted Pragma, the Sorted Header:
let hdrFormatted = "# 𝙵𝙾𝚁𝙼𝙰𝚃 𝚘𝚛𝚐.𝚊𝚝𝚎𝚕𝚒𝚎𝚛𝙸𝚗𝚖𝚞.𝚟𝚌𝚑𝚎𝚠𝚒𝚗𝚐.𝚞𝚜𝚎𝚛𝙻𝚊𝚗𝚐𝚞𝚊𝚐𝚎𝙼𝚘𝚍𝚎𝚕𝙳𝚊𝚝𝚊.𝚏𝚘𝚛𝚖𝚊𝚝𝚝𝚎𝚍\n" // Sorted Header let hdrFormatted = "# 𝙵𝙾𝚁𝙼𝙰𝚃 𝚘𝚛𝚐.𝚊𝚝𝚎𝚕𝚒𝚎𝚛𝙸𝚗𝚖𝚞.𝚟𝚌𝚑𝚎𝚠𝚒𝚗𝚐.𝚞𝚜𝚎𝚛𝙻𝚊𝚗𝚐𝚞𝚊𝚐𝚎𝙼𝚘𝚍𝚎𝚕𝙳𝚊𝚝𝚊.𝚏𝚘𝚛𝚖𝚊𝚝𝚝𝚎𝚍\n"
strProcessed = hdrFormatted + strProcessed // Add Sorted Header strProcessed = hdrFormatted + strProcessed // Add Sorted Header
// Step 4: Deduplication. // Step 4: Deduplication.

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

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

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
@ -30,17 +37,23 @@ import Cocoa
window?.standardWindowButton(.closeButton)?.isHidden = true window?.standardWindowButton(.closeButton)?.isHidden = true
window?.standardWindowButton(.miniaturizeButton)?.isHidden = true window?.standardWindowButton(.miniaturizeButton)?.isHidden = true
window?.standardWindowButton(.zoomButton)?.isHidden = true window?.standardWindowButton(.zoomButton)?.isHidden = true
guard let installingVersion = Bundle.main.infoDictionary?[kCFBundleVersionKey as String] as? String, guard
let versionString = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String else { let installingVersion = Bundle.main.infoDictionary?[kCFBundleVersionKey as String]
as? String,
let versionString = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
else {
return return
} }
if let copyrightLabel = Bundle.main.localizedInfoDictionary?["NSHumanReadableCopyright"] as? String { if let copyrightLabel = Bundle.main.localizedInfoDictionary?["NSHumanReadableCopyright"]
as? String
{
appCopyrightLabel.stringValue = copyrightLabel appCopyrightLabel.stringValue = copyrightLabel
} }
if let eulaContent = Bundle.main.localizedInfoDictionary?["CFEULAContent"] as? String { if let eulaContent = Bundle.main.localizedInfoDictionary?["CFEULAContent"] as? String {
appEULAContent.string = eulaContent appEULAContent.string = eulaContent
} }
appVersionLabel.stringValue = String(format: "%@ Build %@", versionString, installingVersion) appVersionLabel.stringValue = String(
format: "%@ Build %@", versionString, installingVersion)
} }
@IBAction func btnWiki(_ sender: NSButton) { @IBAction func btnWiki(_ sender: NSButton) {

View File

@ -726,7 +726,7 @@
<key>USE_HFS+_COMPRESSION</key> <key>USE_HFS+_COMPRESSION</key>
<false/> <false/>
<key>VERSION</key> <key>VERSION</key>
<string>1.4.6</string> <string>1.4.7</string>
</dict> </dict>
<key>TYPE</key> <key>TYPE</key>
<integer>0</integer> <integer>0</integer>

View File

@ -20,9 +20,9 @@
5B62A33627AE795800A19448 /* mgrPrefs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A33527AE795800A19448 /* mgrPrefs.swift */; }; 5B62A33627AE795800A19448 /* mgrPrefs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A33527AE795800A19448 /* mgrPrefs.swift */; };
5B62A33827AE79CD00A19448 /* NSStringUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A33727AE79CD00A19448 /* NSStringUtils.swift */; }; 5B62A33827AE79CD00A19448 /* NSStringUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A33727AE79CD00A19448 /* NSStringUtils.swift */; };
5B62A33D27AE7CC100A19448 /* ctlAboutWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A33C27AE7CC100A19448 /* ctlAboutWindow.swift */; }; 5B62A33D27AE7CC100A19448 /* ctlAboutWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A33C27AE7CC100A19448 /* ctlAboutWindow.swift */; };
5B62A34627AE7CD900A19448 /* HorizontalCandidateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A33F27AE7CD900A19448 /* HorizontalCandidateController.swift */; }; 5B62A34627AE7CD900A19448 /* ctlCandidateHorizontal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A33F27AE7CD900A19448 /* ctlCandidateHorizontal.swift */; };
5B62A34727AE7CD900A19448 /* CandidateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A34027AE7CD900A19448 /* CandidateController.swift */; }; 5B62A34727AE7CD900A19448 /* ctlCandidate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A34027AE7CD900A19448 /* ctlCandidate.swift */; };
5B62A34827AE7CD900A19448 /* VerticalCandidateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A34127AE7CD900A19448 /* VerticalCandidateController.swift */; }; 5B62A34827AE7CD900A19448 /* ctlCandidateVertical.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A34127AE7CD900A19448 /* ctlCandidateVertical.swift */; };
5B62A34927AE7CD900A19448 /* TooltipController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A34327AE7CD900A19448 /* TooltipController.swift */; }; 5B62A34927AE7CD900A19448 /* TooltipController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A34327AE7CD900A19448 /* TooltipController.swift */; };
5B62A34A27AE7CD900A19448 /* NotifierController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A34527AE7CD900A19448 /* NotifierController.swift */; }; 5B62A34A27AE7CD900A19448 /* NotifierController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A34527AE7CD900A19448 /* NotifierController.swift */; };
5B62A35327AE89C400A19448 /* InputSourceHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A33127AE792F00A19448 /* InputSourceHelper.swift */; }; 5B62A35327AE89C400A19448 /* InputSourceHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A33127AE792F00A19448 /* InputSourceHelper.swift */; };
@ -31,6 +31,7 @@
5B73FB5E27B2BE1300E9BF49 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5B73FB6027B2BE1300E9BF49 /* InfoPlist.strings */; }; 5B73FB5E27B2BE1300E9BF49 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5B73FB6027B2BE1300E9BF49 /* InfoPlist.strings */; };
5B7BC4B027AFFBE800F66C24 /* frmPrefWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5B7BC4AE27AFFBE800F66C24 /* frmPrefWindow.xib */; }; 5B7BC4B027AFFBE800F66C24 /* frmPrefWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5B7BC4AE27AFFBE800F66C24 /* frmPrefWindow.xib */; };
5BAD0CD527D701F6003D127F /* vChewingKeyLayout.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 5B30F11227BA568800484E24 /* vChewingKeyLayout.bundle */; }; 5BAD0CD527D701F6003D127F /* vChewingKeyLayout.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 5B30F11227BA568800484E24 /* vChewingKeyLayout.bundle */; };
5BB802DA27FABA8300CF1C19 /* ctlInputMethod_Menu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BB802D927FABA8300CF1C19 /* ctlInputMethod_Menu.swift */; };
5BBBB75F27AED54C0023B93A /* Beep.m4a in Resources */ = {isa = PBXBuildFile; fileRef = 5BBBB75D27AED54C0023B93A /* Beep.m4a */; }; 5BBBB75F27AED54C0023B93A /* Beep.m4a in Resources */ = {isa = PBXBuildFile; fileRef = 5BBBB75D27AED54C0023B93A /* Beep.m4a */; };
5BBBB76027AED54C0023B93A /* Fart.m4a in Resources */ = {isa = PBXBuildFile; fileRef = 5BBBB75E27AED54C0023B93A /* Fart.m4a */; }; 5BBBB76027AED54C0023B93A /* Fart.m4a in Resources */ = {isa = PBXBuildFile; fileRef = 5BBBB75E27AED54C0023B93A /* Fart.m4a */; };
5BBBB76B27AED5DB0023B93A /* frmNonModalAlertWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5BBBB76527AED5DB0023B93A /* frmNonModalAlertWindow.xib */; }; 5BBBB76B27AED5DB0023B93A /* frmNonModalAlertWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5BBBB76527AED5DB0023B93A /* frmNonModalAlertWindow.xib */; };
@ -174,9 +175,9 @@
5B62A33527AE795800A19448 /* mgrPrefs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = mgrPrefs.swift; sourceTree = "<group>"; }; 5B62A33527AE795800A19448 /* mgrPrefs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = mgrPrefs.swift; sourceTree = "<group>"; };
5B62A33727AE79CD00A19448 /* NSStringUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSStringUtils.swift; sourceTree = "<group>"; }; 5B62A33727AE79CD00A19448 /* NSStringUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSStringUtils.swift; sourceTree = "<group>"; };
5B62A33C27AE7CC100A19448 /* ctlAboutWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ctlAboutWindow.swift; sourceTree = "<group>"; }; 5B62A33C27AE7CC100A19448 /* ctlAboutWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ctlAboutWindow.swift; sourceTree = "<group>"; };
5B62A33F27AE7CD900A19448 /* HorizontalCandidateController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HorizontalCandidateController.swift; sourceTree = "<group>"; }; 5B62A33F27AE7CD900A19448 /* ctlCandidateHorizontal.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ctlCandidateHorizontal.swift; sourceTree = "<group>"; };
5B62A34027AE7CD900A19448 /* CandidateController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CandidateController.swift; sourceTree = "<group>"; }; 5B62A34027AE7CD900A19448 /* ctlCandidate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ctlCandidate.swift; sourceTree = "<group>"; };
5B62A34127AE7CD900A19448 /* VerticalCandidateController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerticalCandidateController.swift; sourceTree = "<group>"; }; 5B62A34127AE7CD900A19448 /* ctlCandidateVertical.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ctlCandidateVertical.swift; sourceTree = "<group>"; };
5B62A34327AE7CD900A19448 /* TooltipController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TooltipController.swift; sourceTree = "<group>"; }; 5B62A34327AE7CD900A19448 /* TooltipController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TooltipController.swift; sourceTree = "<group>"; };
5B62A34527AE7CD900A19448 /* NotifierController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotifierController.swift; sourceTree = "<group>"; }; 5B62A34527AE7CD900A19448 /* NotifierController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotifierController.swift; sourceTree = "<group>"; };
5B707CE527D9F3A10099EF99 /* SwiftyOpenCC */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = SwiftyOpenCC; path = Packages/SwiftyOpenCC; sourceTree = "<group>"; }; 5B707CE527D9F3A10099EF99 /* SwiftyOpenCC */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = SwiftyOpenCC; path = Packages/SwiftyOpenCC; sourceTree = "<group>"; };
@ -187,6 +188,7 @@
5B7BC4AF27AFFBE800F66C24 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Source/WindowNIBs/Base.lproj/frmPrefWindow.xib; sourceTree = "<group>"; }; 5B7BC4AF27AFFBE800F66C24 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Source/WindowNIBs/Base.lproj/frmPrefWindow.xib; sourceTree = "<group>"; };
5B7BC4B227AFFC0B00F66C24 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = Source/WindowNIBs/en.lproj/frmPrefWindow.strings; sourceTree = "<group>"; }; 5B7BC4B227AFFC0B00F66C24 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = Source/WindowNIBs/en.lproj/frmPrefWindow.strings; sourceTree = "<group>"; };
5B8F43ED27C9BC220069AC27 /* SymbolLM.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SymbolLM.h; sourceTree = "<group>"; }; 5B8F43ED27C9BC220069AC27 /* SymbolLM.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SymbolLM.h; sourceTree = "<group>"; };
5BB802D927FABA8300CF1C19 /* ctlInputMethod_Menu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ctlInputMethod_Menu.swift; sourceTree = "<group>"; };
5BBBB75D27AED54C0023B93A /* Beep.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = Beep.m4a; sourceTree = "<group>"; }; 5BBBB75D27AED54C0023B93A /* Beep.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = Beep.m4a; sourceTree = "<group>"; };
5BBBB75E27AED54C0023B93A /* Fart.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = Fart.m4a; sourceTree = "<group>"; }; 5BBBB75E27AED54C0023B93A /* Fart.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = Fart.m4a; sourceTree = "<group>"; };
5BBBB76627AED5DB0023B93A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/frmNonModalAlertWindow.xib; sourceTree = "<group>"; }; 5BBBB76627AED5DB0023B93A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/frmNonModalAlertWindow.xib; sourceTree = "<group>"; };
@ -409,6 +411,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
D4A13D5927A59D5C003BE359 /* ctlInputMethod.swift */, D4A13D5927A59D5C003BE359 /* ctlInputMethod.swift */,
5BB802D927FABA8300CF1C19 /* ctlInputMethod_Menu.swift */,
5B62A33127AE792F00A19448 /* InputSourceHelper.swift */, 5B62A33127AE792F00A19448 /* InputSourceHelper.swift */,
5B62A33527AE795800A19448 /* mgrPrefs.swift */, 5B62A33527AE795800A19448 /* mgrPrefs.swift */,
); );
@ -505,9 +508,9 @@
5B62A33E27AE7CD900A19448 /* CandidateUI */ = { 5B62A33E27AE7CD900A19448 /* CandidateUI */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
5B62A33F27AE7CD900A19448 /* HorizontalCandidateController.swift */, 5B62A34027AE7CD900A19448 /* ctlCandidate.swift */,
5B62A34027AE7CD900A19448 /* CandidateController.swift */, 5B62A33F27AE7CD900A19448 /* ctlCandidateHorizontal.swift */,
5B62A34127AE7CD900A19448 /* VerticalCandidateController.swift */, 5B62A34127AE7CD900A19448 /* ctlCandidateVertical.swift */,
); );
path = CandidateUI; path = CandidateUI;
sourceTree = "<group>"; sourceTree = "<group>";
@ -984,12 +987,13 @@
5B5E535227EF261400C6AA1E /* IME.swift in Sources */, 5B5E535227EF261400C6AA1E /* IME.swift in Sources */,
5B62A34927AE7CD900A19448 /* TooltipController.swift in Sources */, 5B62A34927AE7CD900A19448 /* TooltipController.swift in Sources */,
6A0D4F4515FC0EB100ABF4B3 /* Mandarin.cpp in Sources */, 6A0D4F4515FC0EB100ABF4B3 /* Mandarin.cpp in Sources */,
5B62A34827AE7CD900A19448 /* VerticalCandidateController.swift in Sources */, 5B62A34827AE7CD900A19448 /* ctlCandidateVertical.swift in Sources */,
6ACC3D452793701600F1B140 /* ParselessLM.cpp in Sources */, 6ACC3D452793701600F1B140 /* ParselessLM.cpp in Sources */,
5BBBB77A27AEDC690023B93A /* clsSFX.swift in Sources */, 5BBBB77A27AEDC690023B93A /* clsSFX.swift in Sources */,
5BF8423127BAA942008E7E4C /* vChewingKanjiConverter.swift in Sources */, 5BF8423127BAA942008E7E4C /* vChewingKanjiConverter.swift in Sources */,
5B62A34627AE7CD900A19448 /* HorizontalCandidateController.swift in Sources */, 5B62A34627AE7CD900A19448 /* ctlCandidateHorizontal.swift in Sources */,
5B62A34727AE7CD900A19448 /* CandidateController.swift in Sources */, 5B62A34727AE7CD900A19448 /* ctlCandidate.swift in Sources */,
5BB802DA27FABA8300CF1C19 /* ctlInputMethod_Menu.swift in Sources */,
D41355DE278EA3ED005E5CBD /* UserPhrasesLM.mm in Sources */, D41355DE278EA3ED005E5CBD /* UserPhrasesLM.mm in Sources */,
6ACC3D3F27914F2400F1B140 /* KeyValueBlobReader.cpp in Sources */, 6ACC3D3F27914F2400F1B140 /* KeyValueBlobReader.cpp in Sources */,
D41355D8278D74B5005E5CBD /* mgrLangModel.mm in Sources */, D41355D8278D74B5005E5CBD /* mgrLangModel.mm in Sources */,
@ -1187,7 +1191,7 @@
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1947; CURRENT_PROJECT_VERSION = 1948;
DEBUG_INFORMATION_FORMAT = dwarf; DEBUG_INFORMATION_FORMAT = dwarf;
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO; GCC_DYNAMIC_NO_PIC = NO;
@ -1210,7 +1214,7 @@
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 10.11.5; MACOSX_DEPLOYMENT_TARGET = 10.11.5;
MARKETING_VERSION = 1.4.6; MARKETING_VERSION = 1.4.7;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.vChewing.vChewingPhraseEditor; PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.vChewing.vChewingPhraseEditor;
@ -1243,7 +1247,7 @@
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1947; CURRENT_PROJECT_VERSION = 1948;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO; ENABLE_NS_ASSERTIONS = NO;
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
@ -1262,7 +1266,7 @@
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 10.11.5; MACOSX_DEPLOYMENT_TARGET = 10.11.5;
MARKETING_VERSION = 1.4.6; MARKETING_VERSION = 1.4.7;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.vChewing.vChewingPhraseEditor; PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.vChewing.vChewingPhraseEditor;
@ -1377,7 +1381,7 @@
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1947; CURRENT_PROJECT_VERSION = 1948;
DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
GCC_C_LANGUAGE_STANDARD = gnu99; GCC_C_LANGUAGE_STANDARD = gnu99;
@ -1412,7 +1416,7 @@
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 10.11.5; MACOSX_DEPLOYMENT_TARGET = 10.11.5;
MARKETING_VERSION = 1.4.6; MARKETING_VERSION = 1.4.7;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.inputmethod.vChewing; PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.inputmethod.vChewing;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
@ -1445,7 +1449,7 @@
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1947; CURRENT_PROJECT_VERSION = 1948;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
@ -1475,7 +1479,7 @@
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 10.11.5; MACOSX_DEPLOYMENT_TARGET = 10.11.5;
MARKETING_VERSION = 1.4.6; MARKETING_VERSION = 1.4.7;
PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.inputmethod.vChewing; PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.inputmethod.vChewing;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
@ -1559,7 +1563,7 @@
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1947; CURRENT_PROJECT_VERSION = 1948;
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
GCC_C_LANGUAGE_STANDARD = gnu99; GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO; GCC_DYNAMIC_NO_PIC = NO;
@ -1584,7 +1588,7 @@
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 10.11.5; MACOSX_DEPLOYMENT_TARGET = 10.11.5;
MARKETING_VERSION = 1.4.6; MARKETING_VERSION = 1.4.7;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "org.atelierInmu.vChewing.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_BUNDLE_IDENTIFIER = "org.atelierInmu.vChewing.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
@ -1612,7 +1616,7 @@
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1947; CURRENT_PROJECT_VERSION = 1948;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
GCC_C_LANGUAGE_STANDARD = gnu99; GCC_C_LANGUAGE_STANDARD = gnu99;
@ -1632,7 +1636,7 @@
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 10.11.5; MACOSX_DEPLOYMENT_TARGET = 10.11.5;
MARKETING_VERSION = 1.4.6; MARKETING_VERSION = 1.4.7;
PRODUCT_BUNDLE_IDENTIFIER = "org.atelierInmu.vChewing.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_BUNDLE_IDENTIFIER = "org.atelierInmu.vChewing.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";