Swift // Clang-Format.

This commit is contained in:
ShikiSuen 2022-04-03 11:23:50 +08:00
parent 63c6a3e4be
commit 1fe13c1220
34 changed files with 7142 additions and 6281 deletions

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
}

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
} }
@ -51,7 +61,7 @@ precedencegroup ExponentiationPrecedence {
higherThan: MultiplicationPrecedence higherThan: MultiplicationPrecedence
} }
infix operator ** : ExponentiationPrecedence infix operator **: ExponentiationPrecedence
func ** (_ base: Double, _ exp: Double) -> Double { func ** (_ base: Double, _ exp: Double) -> Double {
return pow(base, exp) return pow(base, exp)
@ -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: " ")
@ -144,7 +154,7 @@ func rawDictForPhrases(isCHS: Bool) -> [Entry] {
} }
} }
// Entry // Entry
let arrCells : [String] = varLineDataProcessed.components(separatedBy: "\t") let arrCells: [String] = varLineDataProcessed.components(separatedBy: "\t")
count = 0 // count = 0 //
var phone = "" var phone = ""
var phrase = "" var phrase = ""
@ -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 = ""
@ -212,7 +230,7 @@ func rawDictForKanjis(isCHS: Bool) -> [Entry] {
} }
} }
// Entry // Entry
let arrCells : [String] = varLineDataProcessed.components(separatedBy: "\t") let arrCells: [String] = varLineDataProcessed.components(separatedBy: "\t")
count = 0 // count = 0 //
var phone = "" var phone = ""
var phrase = "" var phrase = ""
@ -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
@ -281,7 +304,7 @@ func rawDictForNonKanjis(isCHS: Bool) -> [Entry] {
} }
} }
// Entry // Entry
let arrCells : [String] = varLineDataProcessed.components(separatedBy: "\t") let arrCells: [String] = varLineDataProcessed.components(separatedBy: "\t")
count = 0 // count = 0 //
var phone = "" var phone = ""
var phrase = "" var phrase = ""
@ -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
@ -35,7 +44,7 @@ private let kTranslocationRemovalTickInterval: TimeInterval = 0.5
private let kTranslocationRemovalDeadline: TimeInterval = 60.0 private let kTranslocationRemovalDeadline: TimeInterval = 60.0
@NSApplicationMain @NSApplicationMain
@objc (AppDelegate) @objc(AppDelegate)
class AppDelegate: NSWindowController, NSApplicationDelegate { class AppDelegate: NSWindowController, NSApplicationDelegate {
@IBOutlet weak private var installButton: NSButton! @IBOutlet weak private var installButton: NSButton!
@IBOutlet weak private var cancelButton: NSButton! @IBOutlet weak private var cancelButton: NSButton!
@ -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,12 +42,14 @@ 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
} }
let devModeAppBundlePath = (resourePath as NSString).appendingPathComponent(targetAppBundleName) let devModeAppBundlePath = (resourePath as NSString).appendingPathComponent(
targetAppBundleName)
let count = notarizedArchivesContent.count let count = notarizedArchivesContent.count
let notarizedArchiveExists = FileManager.default.fileExists(atPath: notarizedArchive) let notarizedArchiveExists = FileManager.default.fileExists(atPath: notarizedArchive)
let devModeAppBundleExists = FileManager.default.fileExists(atPath: devModeAppBundlePath) let devModeAppBundleExists = FileManager.default.fileExists(atPath: devModeAppBundlePath)
@ -50,8 +59,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 +88,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 +104,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 +114,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 Foundation import Foundation

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
@ -53,7 +60,7 @@ import Cocoa
class InputState: NSObject { class InputState: NSObject {
/// Represents that the input controller is deactivated. /// Represents that the input controller is deactivated.
@objc (InputStateDeactivated) @objc(InputStateDeactivated)
class Deactivated: InputState { class Deactivated: InputState {
override var description: String { override var description: String {
"<InputState.Deactivated>" "<InputState.Deactivated>"
@ -63,7 +70,7 @@ class InputState: NSObject {
// MARK: - // MARK: -
/// Represents that the composing buffer is empty. /// Represents that the composing buffer is empty.
@objc (InputStateEmpty) @objc(InputStateEmpty)
class Empty: InputState { class Empty: InputState {
@objc var composingBuffer: String { @objc var composingBuffer: String {
"" ""
@ -77,7 +84,7 @@ class InputState: NSObject {
// MARK: - // MARK: -
/// Represents that the composing buffer is empty. /// Represents that the composing buffer is empty.
@objc (InputStateEmptyIgnoringPreviousState) @objc(InputStateEmptyIgnoringPreviousState)
class EmptyIgnoringPreviousState: InputState { class EmptyIgnoringPreviousState: InputState {
@objc var composingBuffer: String { @objc var composingBuffer: String {
"" ""
@ -90,7 +97,7 @@ class InputState: NSObject {
// MARK: - // MARK: -
/// Represents that the input controller is committing text into client app. /// Represents that the input controller is committing text into client app.
@objc (InputStateCommitting) @objc(InputStateCommitting)
class Committing: InputState { class Committing: InputState {
@objc private(set) var poppedText: String = "" @objc private(set) var poppedText: String = ""
@ -107,7 +114,7 @@ class InputState: NSObject {
// MARK: - // MARK: -
/// Represents that the composing buffer is not empty. /// Represents that the composing buffer is not empty.
@objc (InputStateNotEmpty) @objc(InputStateNotEmpty)
class NotEmpty: InputState { class NotEmpty: InputState {
@objc private(set) var composingBuffer: String @objc private(set) var composingBuffer: String
@objc private(set) var cursorIndex: UInt @objc private(set) var cursorIndex: UInt
@ -125,7 +132,7 @@ class InputState: NSObject {
// MARK: - // MARK: -
/// Represents that the user is inputting text. /// Represents that the user is inputting text.
@objc (InputStateInputting) @objc(InputStateInputting)
class Inputting: NotEmpty { class Inputting: NotEmpty {
@objc var poppedText: String = "" @objc var poppedText: String = ""
@objc var tooltip: String = "" @objc var tooltip: String = ""
@ -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
} }
@ -153,7 +162,7 @@ class InputState: NSObject {
private let kMaxMarkRangeLength = mgrPrefs.maxCandidateLength private let kMaxMarkRangeLength = mgrPrefs.maxCandidateLength
/// Represents that the user is marking a range in the composing buffer. /// Represents that the user is marking a range in the composing buffer.
@objc (InputStateMarking) @objc(InputStateMarking)
class Marking: NotEmpty { class Marking: NotEmpty {
@objc private(set) var markerIndex: UInt @objc private(set) var markerIndex: UInt
@ -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,35 +191,57 @@ 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 = ""
@objc private(set) var readings: [String] @objc private(set) var readings: [String]
@objc init(composingBuffer: String, cursorIndex: UInt, markerIndex: UInt, readings: [String]) { @objc init(
composingBuffer: String, cursorIndex: UInt, markerIndex: UInt, readings: [String]
) {
self.markerIndex = markerIndex self.markerIndex = markerIndex
let begin = min(cursorIndex, markerIndex) let begin = min(cursorIndex, markerIndex)
let end = max(cursorIndex, markerIndex) let end = max(cursorIndex, markerIndex)
@ -219,18 +254,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 +302,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 = "#𝙊𝙥𝙚𝙣𝘾𝘾"
@ -297,21 +348,25 @@ class InputState: NSObject {
// MARK: - // MARK: -
/// Represents that the user is choosing in a candidates list. /// Represents that the user is choosing in a candidates list.
@objc (InputStateChoosingCandidate) @objc(InputStateChoosingCandidate)
class ChoosingCandidate: NotEmpty { class ChoosingCandidate: NotEmpty {
@objc private(set) var candidates: [String] @objc private(set) var candidates: [String]
@objc private(set) var useVerticalMode: Bool @objc private(set) var useVerticalMode: Bool
@objc init(composingBuffer: String, cursorIndex: UInt, candidates: [String], useVerticalMode: Bool) { @objc init(
composingBuffer: String, cursorIndex: UInt, candidates: [String], useVerticalMode: Bool
) {
self.candidates = candidates self.candidates = candidates
self.useVerticalMode = useVerticalMode self.useVerticalMode = useVerticalMode
super.init(composingBuffer: composingBuffer, cursorIndex: cursorIndex) super.init(composingBuffer: composingBuffer, cursorIndex: cursorIndex)
} }
@objc var attributedString: NSAttributedString { @objc var attributedString: NSAttributedString {
let attributedSting = NSAttributedString(string: composingBuffer, attributes: [ let attributedSting = NSAttributedString(
string: composingBuffer,
attributes: [
.underlineStyle: NSUnderlineStyle.single.rawValue, .underlineStyle: NSUnderlineStyle.single.rawValue,
.markedClauseSegment: 0 .markedClauseSegment: 0,
]) ])
return attributedSting return attributedSting
} }
@ -325,7 +380,7 @@ class InputState: NSObject {
/// Represents that the user is choosing in a candidates list /// Represents that the user is choosing in a candidates list
/// in the associated phrases mode. /// in the associated phrases mode.
@objc (InputStateAssociatedPhrases) @objc(InputStateAssociatedPhrases)
class AssociatedPhrases: InputState { class AssociatedPhrases: InputState {
@objc private(set) var candidates: [String] = [] @objc private(set) var candidates: [String] = []
@objc private(set) var useVerticalMode: Bool = false @objc private(set) var useVerticalMode: Bool = false
@ -340,14 +395,16 @@ class InputState: NSObject {
} }
} }
@objc (InputStateSymbolTable) @objc(InputStateSymbolTable)
class SymbolTable: ChoosingCandidate { class SymbolTable: ChoosingCandidate {
@objc var node: SymbolNode @objc var node: SymbolNode
@objc init(node: SymbolNode, useVerticalMode: Bool) { @objc init(node: SymbolNode, useVerticalMode: Bool) {
self.node = node self.node = node
let candidates = node.children?.map { $0.title } ?? [String]() let candidates = node.children?.map { $0.title } ?? [String]()
super.init(composingBuffer: "", cursorIndex: 0, candidates: candidates, useVerticalMode: useVerticalMode) super.init(
composingBuffer: "", cursorIndex: 0, candidates: candidates,
useVerticalMode: useVerticalMode)
} }
override var description: String { override var description: String {
@ -373,38 +430,57 @@ 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(
SymbolNode(catMathSymbols, symbols:"+-×÷=≠≒∞±√<>﹤﹥≦≧∩∪ˇ⊥∠∟⊿㏒㏑∫∮∵∴╳﹢"), catGreekLetters, symbols: "αβγδεζηθικλμνξοπρστυφχψωΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ"),
SymbolNode(catCurrencyUnits, symbols:"$€¥¢£₽₨₩฿₺₮₱₭₴₦৲৳૱௹﷼₹₲₪₡₫៛₵₢₸₤₳₥₠₣₰₧₯₶₷"), SymbolNode(catMathSymbols, symbols: "+-×÷=≠≒∞±√<>﹤﹥≦≧∩∪ˇ⊥∠∟⊿㏒㏑∫∮∵∴╳﹢"),
SymbolNode(catSpecialSymbols, symbols:"↑↓←→↖↗↙↘↺⇧⇩⇦⇨⇄⇆⇅⇵↻◎○●⊕⊙※△▲☆★◇◆□■▽▼§¥〒¢£♀♂↯"), SymbolNode(catCurrencyUnits, symbols: "$€¥¢£₽₨₩฿₺₮₱₭₴₦৲৳૱௹﷼₹₲₪₡₫៛₵₢₸₤₳₥₠₣₰₧₯₶₷"),
SymbolNode(catUnicodeSymbols, symbols:"♨☀☁☂☃♠♥♣♦♩♪♫♬☺☻"), SymbolNode(catSpecialSymbols, symbols: "↑↓←→↖↗↙↘↺⇧⇩⇦⇨⇄⇆⇅⇵↻◎○●⊕⊙※△▲☆★◇◆□■▽▼§¥〒¢£♀♂↯"),
SymbolNode(catCircledKanjis, symbols:"㊟㊞㊚㊛㊊㊋㊌㊍㊎㊏㊐㊑㊒㊓㊔㊕㊖㊗︎㊘㊙︎㊜㊝㊠㊡㊢㊣㊤㊥㊦㊧㊨㊩㊪㊫㊬㊭㊮㊯㊰🈚︎🈯︎"), SymbolNode(catUnicodeSymbols, symbols: "♨☀☁☂☃♠♥♣♦♩♪♫♬☺☻"),
SymbolNode(catCircledKataKana, symbols:"㋐㋑㋒㋓㋔㋕㋖㋗㋘㋙㋚㋛㋜㋝㋞㋟㋠㋡㋢㋣㋤㋥㋦㋧㋨㋩㋪㋫㋬㋭㋮㋯㋰㋱㋲㋳㋴㋵㋶㋷㋸㋹㋺㋻㋼㋾"), SymbolNode(catCircledKanjis, symbols: "㊟㊞㊚㊛㊊㊋㊌㊍㊎㊏㊐㊑㊒㊓㊔㊕㊖㊗︎㊘㊙︎㊜㊝㊠㊡㊢㊣㊤㊥㊦㊧㊨㊩㊪㊫㊬㊭㊮㊯㊰🈚︎🈯︎"),
SymbolNode(catBracketKanjis, symbols:"㈪㈫㈬㈭㈮㈯㈰㈱㈲㈳㈴㈵㈶㈷㈸㈹㈺㈻㈼㈽㈾㈿㉀㉁㉂㉃"), SymbolNode(
SymbolNode(catSingleTableLines, symbols:"├─┼┴┬┤┌┐╞═╪╡│▕└┘╭╮╰╯"), catCircledKataKana, symbols: "㋐㋑㋒㋓㋔㋕㋖㋗㋘㋙㋚㋛㋜㋝㋞㋟㋠㋡㋢㋣㋤㋥㋦㋧㋨㋩㋪㋫㋬㋭㋮㋯㋰㋱㋲㋳㋴㋵㋶㋷㋸㋹㋺㋻㋼㋾"),
SymbolNode(catDoubleTableLines, symbols:"╔╦╗╠═╬╣╓╥╖╒╤╕║╚╩╝╟╫╢╙╨╜╞╪╡╘╧╛"), SymbolNode(catBracketKanjis, symbols: "㈪㈫㈬㈭㈮㈯㈰㈱㈲㈳㈴㈵㈶㈷㈸㈹㈺㈻㈼㈽㈾㈿㉀㉁㉂㉃"),
SymbolNode(catFillingBlocks, symbols:"_ˍ▁▂▃▄▅▆▇█▏▎▍▌▋▊▉◢◣◥◤"), SymbolNode(catSingleTableLines, symbols: "├─┼┴┬┤┌┐╞═╪╡│▕└┘╭╮╰╯"),
SymbolNode(catLineSegments, symbols:"﹣﹦≡|∣∥–︱—︳╴¯ ̄﹉﹊﹍﹎﹋﹌﹏︴∕﹨╱╲/\"), SymbolNode(catDoubleTableLines, symbols: "╔╦╗╠═╬╣╓╥╖╒╤╕║╚╩╝╟╫╢╙╨╜╞╪╡╘╧╛"),
SymbolNode(catFillingBlocks, symbols: "_ˍ▁▂▃▄▅▆▇█▏▎▍▌▋▊▉◢◣◥◤"),
SymbolNode(catLineSegments, symbols: "﹣﹦≡|∣∥–︱—︳╴¯ ̄﹉﹊﹍﹎﹋﹌﹏︴∕﹨╱╲/\"),
]) ])
} }

View File

@ -1,20 +1,27 @@
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). // Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License).
// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). // All possible vChewing-specific modifications are of:
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
/* /*
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 1. The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. marks, or product names of Contributor, except as required to fulfill notice
requirements above.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
import Cocoa import Cocoa
@ -45,17 +52,17 @@ 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.
} }
class keyParser: NSObject { class keyParser: NSObject {
@objc private (set) var useVerticalMode: Bool @objc private(set) var useVerticalMode: Bool
@objc private (set) var inputText: String? @objc private(set) var inputText: String?
@objc private (set) var inputTextIgnoringModifiers: String? @objc private(set) var inputTextIgnoringModifiers: String?
@objc private (set) var charCode: UInt16 @objc private(set) var charCode: UInt16
@objc private (set) var keyCode: UInt16 @objc private(set) var keyCode: UInt16
private var isFlagChanged: Bool private var isFlagChanged: Bool
private var flags: NSEvent.ModifierFlags private var flags: NSEvent.ModifierFlags
private var cursorForwardKey: KeyCode private var cursorForwardKey: KeyCode
@ -64,11 +71,15 @@ class keyParser: NSObject {
private var extraChooseCandidateKeyReverse: KeyCode private var extraChooseCandidateKeyReverse: KeyCode
private var absorbedArrowKey: KeyCode private var absorbedArrowKey: KeyCode
private var verticalModeOnlyChooseCandidateKey: KeyCode private var verticalModeOnlyChooseCandidateKey: KeyCode
@objc private (set) var emacsKey: vChewingEmacsKey @objc private(set) var emacsKey: vChewingEmacsKey
@objc init(inputText: String?, keyCode: UInt16, charCode: UInt16, flags: NSEvent.ModifierFlags, isVerticalMode: Bool, inputTextIgnoringModifiers: String? = nil) { @objc init(
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,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
@ -23,7 +30,7 @@ public protocol FSEventStreamHelperDelegate: AnyObject {
func helper(_ helper: FSEventStreamHelper, didReceive events: [FSEventStreamHelper.Event]) func helper(_ helper: FSEventStreamHelper, didReceive events: [FSEventStreamHelper.Event])
} }
public class FSEventStreamHelper : NSObject { public class FSEventStreamHelper: NSObject {
public struct Event { public struct Event {
var path: String var path: String
@ -48,17 +55,22 @@ 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])
} }
helper.delegate?.helper(helper, didReceive: events) helper.delegate?.helper(helper, didReceive: events)
}, },
@ -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,26 +1,32 @@
// Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License). // Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License).
/* /*
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 1. The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. marks, or product names of Contributor, except as required to fulfill notice
requirements above.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
import Cocoa import Cocoa
@objc public class IME: NSObject { @objc public class IME: NSObject {
static let dlgOpenPath = NSOpenPanel(); static let dlgOpenPath = NSOpenPanel()
// MARK: - Print debug information to the console. // MARK: - Print debug information to the console.
@objc static func prtDebugIntel(_ strPrint: String) { @objc static func prtDebugIntel(_ strPrint: String) {
@ -50,12 +56,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
} }
@ -69,7 +78,8 @@ import Cocoa
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)")
} }
@ -89,17 +99,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 +149,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 +183,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,27 +1,34 @@
// 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 { extension Bool {
var state: NSControl.StateValue { fileprivate var state: NSControl.StateValue {
self ? .on : .off self ? .on : .off
} }
} }
@ -30,19 +37,19 @@ private let kMinKeyLabelSize: CGFloat = 10
private var gCurrentCandidateController: CandidateController? private var gCurrentCandidateController: CandidateController?
private extension CandidateController { extension CandidateController {
static let horizontal = HorizontalCandidateController() fileprivate static let horizontal = HorizontalCandidateController()
static let vertical = VerticalCandidateController() fileprivate static let vertical = VerticalCandidateController()
} }
@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()
@ -80,66 +87,106 @@ class ctlInputMethod: IMKInputController {
let menu = NSMenu(title: "Input Method Menu") let menu = NSMenu(title: "Input Method Menu")
let useSCPCTypingModeItem = menu.addItem(withTitle: NSLocalizedString("Per-Char Select Mode", comment: ""), action: #selector(toggleSCPCTypingMode(_:)), keyEquivalent: "P") let useSCPCTypingModeItem = menu.addItem(
withTitle: NSLocalizedString("Per-Char Select Mode", comment: ""),
action: #selector(toggleSCPCTypingMode(_:)), keyEquivalent: "P")
useSCPCTypingModeItem.keyEquivalentModifierMask = [.command, .control] useSCPCTypingModeItem.keyEquivalentModifierMask = [.command, .control]
useSCPCTypingModeItem.state = mgrPrefs.useSCPCTypingMode.state useSCPCTypingModeItem.state = mgrPrefs.useSCPCTypingMode.state
let useCNS11643SupportItem = menu.addItem(withTitle: NSLocalizedString("CNS11643 Mode", comment: ""), action: #selector(toggleCNS11643Enabled(_:)), keyEquivalent: "L") let useCNS11643SupportItem = menu.addItem(
withTitle: NSLocalizedString("CNS11643 Mode", comment: ""),
action: #selector(toggleCNS11643Enabled(_:)), keyEquivalent: "L")
useCNS11643SupportItem.keyEquivalentModifierMask = [.command, .control] useCNS11643SupportItem.keyEquivalentModifierMask = [.command, .control]
useCNS11643SupportItem.state = mgrPrefs.cns11643Enabled.state useCNS11643SupportItem.state = mgrPrefs.cns11643Enabled.state
if keyHandler.inputMode == InputMode.imeModeCHT { if keyHandler.inputMode == InputMode.imeModeCHT {
let chineseConversionItem = menu.addItem(withTitle: NSLocalizedString("Force KangXi Writing", comment: ""), action: #selector(toggleChineseConverter(_:)), keyEquivalent: "K") let chineseConversionItem = menu.addItem(
withTitle: NSLocalizedString("Force KangXi Writing", comment: ""),
action: #selector(toggleChineseConverter(_:)), keyEquivalent: "K")
chineseConversionItem.keyEquivalentModifierMask = [.command, .control] chineseConversionItem.keyEquivalentModifierMask = [.command, .control]
chineseConversionItem.state = mgrPrefs.chineseConversionEnabled.state chineseConversionItem.state = mgrPrefs.chineseConversionEnabled.state
let shiftJISConversionItem = menu.addItem(withTitle: NSLocalizedString("JIS Shinjitai Output", comment: ""), action: #selector(toggleShiftJISShinjitaiOutput(_:)), keyEquivalent: "J") let shiftJISConversionItem = menu.addItem(
withTitle: NSLocalizedString("JIS Shinjitai Output", comment: ""),
action: #selector(toggleShiftJISShinjitaiOutput(_:)), keyEquivalent: "J")
shiftJISConversionItem.keyEquivalentModifierMask = [.command, .control] shiftJISConversionItem.keyEquivalentModifierMask = [.command, .control]
shiftJISConversionItem.state = mgrPrefs.shiftJISShinjitaiOutputEnabled.state shiftJISConversionItem.state = mgrPrefs.shiftJISShinjitaiOutputEnabled.state
} }
let halfWidthPunctuationItem = menu.addItem(withTitle: NSLocalizedString("Half-Width Punctuation Mode", comment: ""), action: #selector(toggleHalfWidthPunctuation(_:)), keyEquivalent: "H") let halfWidthPunctuationItem = menu.addItem(
withTitle: NSLocalizedString("Half-Width Punctuation Mode", comment: ""),
action: #selector(toggleHalfWidthPunctuation(_:)), keyEquivalent: "H")
halfWidthPunctuationItem.keyEquivalentModifierMask = [.command, .control] halfWidthPunctuationItem.keyEquivalentModifierMask = [.command, .control]
halfWidthPunctuationItem.state = mgrPrefs.halfWidthPunctuationEnabled.state halfWidthPunctuationItem.state = mgrPrefs.halfWidthPunctuationEnabled.state
let userAssociatedPhrasesItem = menu.addItem(withTitle: NSLocalizedString("Per-Char Associated Phrases", comment: ""), action: #selector(toggleAssociatedPhrasesEnabled(_:)), keyEquivalent: "O") let userAssociatedPhrasesItem = menu.addItem(
withTitle: NSLocalizedString("Per-Char Associated Phrases", comment: ""),
action: #selector(toggleAssociatedPhrasesEnabled(_:)), keyEquivalent: "O")
userAssociatedPhrasesItem.keyEquivalentModifierMask = [.command, .control] userAssociatedPhrasesItem.keyEquivalentModifierMask = [.command, .control]
userAssociatedPhrasesItem.state = mgrPrefs.associatedPhrasesEnabled.state userAssociatedPhrasesItem.state = mgrPrefs.associatedPhrasesEnabled.state
if optionKeyPressed { if optionKeyPressed {
let phaseReplacementItem = menu.addItem(withTitle: NSLocalizedString("Use Phrase Replacement", comment: ""), action: #selector(togglePhraseReplacement(_:)), keyEquivalent: "") let phaseReplacementItem = menu.addItem(
withTitle: NSLocalizedString("Use Phrase Replacement", comment: ""),
action: #selector(togglePhraseReplacement(_:)), keyEquivalent: "")
phaseReplacementItem.state = mgrPrefs.phraseReplacementEnabled.state phaseReplacementItem.state = mgrPrefs.phraseReplacementEnabled.state
let toggleSymbolInputItem = menu.addItem(withTitle: NSLocalizedString("Symbol & Emoji Input", comment: ""), action: #selector(toggleSymbolEnabled(_:)), keyEquivalent: "") let toggleSymbolInputItem = menu.addItem(
withTitle: NSLocalizedString("Symbol & Emoji Input", comment: ""),
action: #selector(toggleSymbolEnabled(_:)), keyEquivalent: "")
toggleSymbolInputItem.state = mgrPrefs.symbolInputEnabled.state toggleSymbolInputItem.state = mgrPrefs.symbolInputEnabled.state
} }
menu.addItem(NSMenuItem.separator()) // --------------------- menu.addItem(NSMenuItem.separator()) // ---------------------
menu.addItem(withTitle: NSLocalizedString("Open User Data Folder", comment: ""), action: #selector(openUserDataFolder(_:)), keyEquivalent: "") menu.addItem(
menu.addItem(withTitle: NSLocalizedString("Edit User Phrases…", comment: ""), action: #selector(openUserPhrases(_:)), keyEquivalent: "") 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 { if optionKeyPressed {
menu.addItem(withTitle: NSLocalizedString("Edit Excluded Phrases…", comment: ""), action: #selector(openExcludedPhrases(_:)), keyEquivalent: "") menu.addItem(
menu.addItem(withTitle: NSLocalizedString("Edit Phrase Replacement Table…", comment: ""), action: #selector(openPhraseReplacement(_:)), keyEquivalent: "") withTitle: NSLocalizedString("Edit Excluded Phrases…", comment: ""),
menu.addItem(withTitle: NSLocalizedString("Edit Associated Phrases…", comment: ""), action: #selector(openAssociatedPhrases(_:)), keyEquivalent: "") action: #selector(openExcludedPhrases(_:)), keyEquivalent: "")
menu.addItem(withTitle: NSLocalizedString("Edit User Symbol & Emoji Data…", comment: ""), action: #selector(openUserSymbols(_:)), 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) { if optionKeyPressed || !mgrPrefs.shouldAutoReloadUserDataFiles {
menu.addItem(withTitle: NSLocalizedString("Reload User Phrases", comment: ""), action: #selector(reloadUserPhrases(_:)), keyEquivalent: "") menu.addItem(
withTitle: NSLocalizedString("Reload User Phrases", comment: ""),
action: #selector(reloadUserPhrases(_:)), keyEquivalent: "")
} }
menu.addItem(NSMenuItem.separator()) // --------------------- menu.addItem(NSMenuItem.separator()) // ---------------------
menu.addItem(withTitle: NSLocalizedString("vChewing Preferences…", comment: ""), action: #selector(showPreferences(_:)), keyEquivalent: "") menu.addItem(
withTitle: NSLocalizedString("vChewing Preferences…", comment: ""),
action: #selector(showPreferences(_:)), keyEquivalent: "")
if !optionKeyPressed { if !optionKeyPressed {
menu.addItem(withTitle: NSLocalizedString("Check for Updates…", comment: ""), action: #selector(checkForUpdate(_:)), keyEquivalent: "") 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(
menu.addItem(withTitle: NSLocalizedString("About vChewing…", comment: ""), action: #selector(showAbout(_:)), keyEquivalent: "") withTitle: NSLocalizedString("Reboot vChewing…", comment: ""),
action: #selector(selfTerminate(_:)), keyEquivalent: "")
menu.addItem(
withTitle: NSLocalizedString("About vChewing…", comment: ""),
action: #selector(showAbout(_:)), keyEquivalent: "")
if optionKeyPressed { if optionKeyPressed {
menu.addItem(withTitle: NSLocalizedString("Uninstall vChewing…", comment: ""), action: #selector(selfUninstall(_:)), keyEquivalent: "") menu.addItem(
withTitle: NSLocalizedString("Uninstall vChewing…", comment: ""),
action: #selector(selfUninstall(_:)), keyEquivalent: "")
} }
// NSMenu modified key // NSMenu modified key
@ -216,10 +263,14 @@ 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()
== "org.atelierInmu.vChewing.vChewingPhraseEditor"
{
ctlInputMethod.areWeUsingOurOwnPhraseEditor = true ctlInputMethod.areWeUsingOurOwnPhraseEditor = true
} else { } else {
ctlInputMethod.areWeUsingOurOwnPhraseEditor = false ctlInputMethod.areWeUsingOurOwnPhraseEditor = false
@ -243,35 +294,77 @@ class ctlInputMethod: IMKInputController {
} }
@objc func toggleSCPCTypingMode(_ sender: Any?) { @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: ""))) 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?) { @objc func toggleChineseConverter(_ sender: Any?) {
NotifierController.notify(message: String(format: "%@%@%@", NSLocalizedString("Force KangXi Writing", comment: ""), "\n", mgrPrefs.toggleChineseConversionEnabled() ? NSLocalizedString("NotificationSwitchON", comment: "") : NSLocalizedString("NotificationSwitchOFF", comment: ""))) NotifierController.notify(
message: String(
format: "%@%@%@", NSLocalizedString("Force KangXi Writing", comment: ""), "\n",
mgrPrefs.toggleChineseConversionEnabled()
? NSLocalizedString("NotificationSwitchON", comment: "")
: NSLocalizedString("NotificationSwitchOFF", comment: "")))
} }
@objc func toggleShiftJISShinjitaiOutput(_ sender: Any?) { @objc func toggleShiftJISShinjitaiOutput(_ sender: Any?) {
NotifierController.notify(message: String(format: "%@%@%@", NSLocalizedString("JIS Shinjitai Output", comment: ""), "\n", mgrPrefs.toggleShiftJISShinjitaiOutputEnabled() ? NSLocalizedString("NotificationSwitchON", comment: "") : NSLocalizedString("NotificationSwitchOFF", comment: ""))) NotifierController.notify(
message: String(
format: "%@%@%@", NSLocalizedString("JIS Shinjitai Output", comment: ""), "\n",
mgrPrefs.toggleShiftJISShinjitaiOutputEnabled()
? NSLocalizedString("NotificationSwitchON", comment: "")
: NSLocalizedString("NotificationSwitchOFF", comment: "")))
} }
@objc func toggleHalfWidthPunctuation(_ sender: Any?) { @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: ""))) 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?) { @objc func toggleCNS11643Enabled(_ sender: Any?) {
NotifierController.notify(message: String(format: "%@%@%@", NSLocalizedString("CNS11643 Mode", comment: ""), "\n", mgrPrefs.toggleCNS11643Enabled() ? NSLocalizedString("NotificationSwitchON", comment: "") : NSLocalizedString("NotificationSwitchOFF", comment: ""))) NotifierController.notify(
message: String(
format: "%@%@%@", NSLocalizedString("CNS11643 Mode", comment: ""), "\n",
mgrPrefs.toggleCNS11643Enabled()
? NSLocalizedString("NotificationSwitchON", comment: "")
: NSLocalizedString("NotificationSwitchOFF", comment: "")))
} }
@objc func toggleSymbolEnabled(_ sender: Any?) { @objc func toggleSymbolEnabled(_ sender: Any?) {
NotifierController.notify(message: String(format: "%@%@%@", NSLocalizedString("Symbol & Emoji Input", comment: ""), "\n", mgrPrefs.toggleSymbolInputEnabled() ? NSLocalizedString("NotificationSwitchON", comment: "") : NSLocalizedString("NotificationSwitchOFF", comment: ""))) NotifierController.notify(
message: String(
format: "%@%@%@", NSLocalizedString("Symbol & Emoji Input", comment: ""), "\n",
mgrPrefs.toggleSymbolInputEnabled()
? NSLocalizedString("NotificationSwitchON", comment: "")
: NSLocalizedString("NotificationSwitchOFF", comment: "")))
} }
@objc func toggleAssociatedPhrasesEnabled(_ sender: Any?) { @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: ""))) 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?) { @objc func togglePhraseReplacement(_ sender: Any?) {
NotifierController.notify(message: String(format: "%@%@%@", NSLocalizedString("Use Phrase Replacement", comment: ""), "\n", mgrPrefs.togglePhraseReplacementEnabled() ? NSLocalizedString("NotificationSwitchON", comment: "") : NSLocalizedString("NotificationSwitchOFF", comment: ""))) NotifierController.notify(
message: String(
format: "%@%@%@", NSLocalizedString("Use Phrase Replacement", comment: ""), "\n",
mgrPrefs.togglePhraseReplacementEnabled()
? NSLocalizedString("NotificationSwitchON", comment: "")
: NSLocalizedString("NotificationSwitchOFF", comment: "")))
} }
@objc func selfUninstall(_ sender: Any?) { @objc func selfUninstall(_ sender: Any?) {
@ -289,8 +382,14 @@ class ctlInputMethod: IMKInputController {
private func open(userFileAt path: String) { private func open(userFileAt path: String) {
func checkIfUserFilesExist() -> Bool { func checkIfUserFilesExist() -> Bool {
if !mgrLangModel.checkIfUserLanguageModelFilesExist() { if !mgrLangModel.checkIfUserLanguageModelFilesExist() {
let content = String(format: NSLocalizedString("Please check the permission at \"%@\".", comment: ""), mgrLangModel.dataFolderPath(isDefaultFolder: false)) let content = String(
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) 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) NSApp.setActivationPolicy(.accessory)
return false return false
} }
@ -311,7 +410,8 @@ class ctlInputMethod: IMKInputController {
if !mgrLangModel.checkIfUserDataFolderExists() { if !mgrLangModel.checkIfUserDataFolderExists() {
return return
} }
NSWorkspace.shared.openFile(mgrLangModel.dataFolderPath(isDefaultFolder: false), withApplication: "Finder") NSWorkspace.shared.openFile(
mgrLangModel.dataFolderPath(isDefaultFolder: false), withApplication: "Finder")
} }
@objc func openExcludedPhrases(_ sender: Any?) { @objc func openExcludedPhrases(_ sender: Any?) {
@ -393,7 +493,8 @@ 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?) {
@ -406,7 +507,9 @@ 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 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?) {
@ -420,10 +523,14 @@ 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(
state: InputState.EmptyIgnoringPreviousState, previous: InputState, client: Any!
) {
gCurrentCandidateController?.visible = false gCurrentCandidateController?.visible = false
hideTooltip() hideTooltip()
@ -431,7 +538,9 @@ extension ctlInputMethod {
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?) {
@ -446,7 +555,9 @@ 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?) {
@ -464,9 +575,13 @@ 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)
} }
} }
@ -479,12 +594,16 @@ 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)
} }
} }
@ -497,7 +616,9 @@ 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))
show(candidateWindowWith: state, client: client) show(candidateWindowWith: state, client: client)
} }
@ -507,7 +628,9 @@ extension ctlInputMethod {
gCurrentCandidateController?.visible = false gCurrentCandidateController?.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)
} }
} }
@ -563,21 +686,27 @@ 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 {
return NSFont(name: name, size: size) ?? finalReturnFont return NSFont(name: name, size: size) ?? finalReturnFont
} }
return finalReturnFont return finalReturnFont
} }
gCurrentCandidateController?.keyLabelFont = labelFont(name: mgrPrefs.candidateKeyLabelFontName, size: keyLabelSize) gCurrentCandidateController?.keyLabelFont = labelFont(
gCurrentCandidateController?.candidateFont = candidateFont(name: mgrPrefs.candidateTextFontName, size: textSize) name: mgrPrefs.candidateKeyLabelFontName, size: keyLabelSize)
gCurrentCandidateController?.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 { gCurrentCandidateController?.keyLabels = keyLabels.map {
CandidateKeyLabel(key: String($0), displayedText: String($0) + keyLabelSuffix) CandidateKeyLabel(key: String($0), displayedText: String($0) + keyLabelSuffix)
@ -600,14 +729,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) gCurrentCandidateController?.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) gCurrentCandidateController?.set(
windowTopLeftPoint: NSMakePoint(
lineHeightRect.origin.x, lineHeightRect.origin.y - 4.0),
bottomOutOfScreenAdjustmentHeight: lineHeightRect.size.height + 4.0)
} }
} }
@ -618,7 +755,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)
@ -642,22 +780,35 @@ extension ctlInputMethod: KeyHandlerDelegate {
gCurrentCandidateController ?? .vertical gCurrentCandidateController ?? .vertical
} }
func keyHandler(_ keyHandler: KeyHandler, didSelectCandidateAt index: Int, candidateController controller: Any) { func keyHandler(
_ keyHandler: KeyHandler, didSelectCandidateAt index: Int,
candidateController controller: Any
) {
if let controller = controller as? CandidateController { if let controller = controller as? CandidateController {
self.candidateController(controller, didSelectCandidateAtIndex: UInt(index)) self.candidateController(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
} }
} }
@ -674,7 +825,9 @@ extension ctlInputMethod: CandidateControllerDelegate {
return 0 return 0
} }
func candidateController(_ controller: CandidateController, candidateAtIndex index: UInt) -> String { func candidateController(_ controller: CandidateController, 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 +836,18 @@ extension ctlInputMethod: CandidateControllerDelegate {
return "" return ""
} }
func candidateController(_ controller: CandidateController, didSelectCandidateAtIndex index: UInt) { func candidateController(
_ controller: CandidateController, 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 +868,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 +886,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 +897,3 @@ extension ctlInputMethod: CandidateControllerDelegate {
} }
} }
} }

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
@ -153,13 +160,13 @@ struct ComposingBufferSize {
case eten = 1 case eten = 1
case hsu = 2 case hsu = 2
case eten26 = 3 case eten26 = 3
case IBM = 4 case ibm = 4
case MiTAC = 5 case mitac = 5
case FakeSeigyou = 6 case fakeSeigyou = 6
case hanyuPinyin = 10 case hanyuPinyin = 10
var name: String { var name: String {
switch (self) { switch self {
case .standard: case .standard:
return "Standard" return "Standard"
case .eten: case .eten:
@ -168,11 +175,11 @@ struct ComposingBufferSize {
return "Hsu" return "Hsu"
case .eten26: case .eten26:
return "ETen26" return "ETen26"
case .IBM: case .ibm:
return "IBM" return "IBM"
case .MiTAC: case .mitac:
return "MiTAC" return "MiTAC"
case .FakeSeigyou: case .fakeSeigyou:
return "FakeSeigyou" return "FakeSeigyou"
case .hanyuPinyin: case .hanyuPinyin:
return "HanyuPinyin" return "HanyuPinyin"
@ -182,8 +189,9 @@ 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,10 +219,11 @@ struct ComposingBufferSize {
kUseSCPCTypingMode, kUseSCPCTypingMode,
kMaxCandidateLength, kMaxCandidateLength,
kShouldNotFartInLieuOfBeep, kShouldNotFartInLieuOfBeep,
kAssociatedPhrasesEnabled] kAssociatedPhrasesEnabled,
]
} }
@objc public static func setMissingDefaults () { @objc public static func setMissingDefaults() {
// Preferences Module plist private // Preferences Module plist private
// //
@ -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,7 +354,7 @@ 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
@ -335,7 +363,8 @@ struct ComposingBufferSize {
(KeyboardLayout(rawValue: self.keyboardLayout) ?? KeyboardLayout.standard).name (KeyboardLayout(rawValue: self.keyboardLayout) ?? KeyboardLayout.standard).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

@ -235,7 +235,7 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing
[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"];

View File

@ -1,27 +1,33 @@
// Copyright (c) 2022 and onwards Isaac Xen (MIT License). // Copyright (c) 2022 and onwards Isaac Xen (MIT License).
// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License). // All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License).
/* /*
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated Permission is hereby granted, free of charge, to any person obtaining a copy of
documentation files (the "Software"), to deal in the Software without restriction, including without limitation this software and associated documentation files (the "Software"), to deal in
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and the Software without restriction, including without limitation the rights to
to permit persons to whom the Software is furnished to do so, subject to the following conditions: use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 1. The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, 2. No trademark license is granted to use the trade names, trademarks, service
except as required to fulfill notice requirements above. marks, or product names of Contributor, except as required to fulfill notice
requirements above.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
import Cocoa import Cocoa
@objc public class clsSFX: NSObject, NSSoundDelegate { @objc public class clsSFX: NSObject, NSSoundDelegate {
private static let shared = clsSFX() private static let shared = clsSFX()
private override init(){ private override init() {
super.init() super.init()
} }
private var currentBeep: NSSound? private var currentBeep: NSSound?
@ -33,14 +39,14 @@ import Cocoa
} }
} }
// Create a new beep sound if possible // Create a new beep sound if possible
var sndBeep:String var sndBeep: String
if mgrPrefs.shouldNotFartInLieuOfBeep == false { if mgrPrefs.shouldNotFartInLieuOfBeep == false {
sndBeep = "Fart" sndBeep = "Fart"
} else { } else {
sndBeep = "Beep" sndBeep = "Beep"
} }
guard guard
let beep = NSSound(named:sndBeep) let beep = NSSound(named: sndBeep)
else { else {
NSSound.beep() NSSound.beep()
return return

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,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,8 +41,10 @@ public class CandidateKeyLabel: NSObject {
@objc(VTCandidateControllerDelegate) @objc(VTCandidateControllerDelegate)
public protocol CandidateControllerDelegate: AnyObject { public protocol CandidateControllerDelegate: AnyObject {
func candidateCountForController(_ controller: CandidateController) -> UInt func candidateCountForController(_ controller: CandidateController) -> UInt
func candidateController(_ controller: CandidateController, candidateAtIndex index: UInt) -> String func candidateController(_ controller: CandidateController, candidateAtIndex index: UInt)
func candidateController(_ controller: CandidateController, didSelectCandidateAtIndex index: UInt) -> String
func candidateController(
_ controller: CandidateController, didSelectCandidateAtIndex index: UInt)
} }
@objc(VTCandidateController) @objc(VTCandidateController)
@ -70,10 +79,12 @@ public class CandidateController: NSWindowController {
} }
} }
@objc public var keyLabels: [CandidateKeyLabel] = ["1", "2", "3", "4", "5", "6", "7", "8", "9"].map { @objc public var keyLabels: [CandidateKeyLabel] = ["1", "2", "3", "4", "5", "6", "7", "8", "9"]
.map {
CandidateKeyLabel(key: $0, displayedText: $0) CandidateKeyLabel(key: $0, displayedText: $0)
} }
@objc public var keyLabelFont: NSFont = NSFont.monospacedDigitSystemFont(ofSize: 14, weight: .medium) @objc public var keyLabelFont: NSFont = NSFont.monospacedDigitSystemFont(
ofSize: 14, weight: .medium)
@objc public var candidateFont: NSFont = NSFont.systemFont(ofSize: 18) @objc public var candidateFont: NSFont = NSFont.systemFont(ofSize: 18)
@objc public var tooltip: String = "" @objc public var tooltip: String = ""
@ -113,7 +124,8 @@ public class CandidateController: NSWindowController {
@objc(setWindowTopLeftPoint:bottomOutOfScreenAdjustmentHeight:) @objc(setWindowTopLeftPoint:bottomOutOfScreenAdjustmentHeight:)
public func set(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight height: CGFloat) { public func set(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight height: CGFloat) {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) { DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) {
self.doSet(windowTopLeftPoint: windowTopLeftPoint, bottomOutOfScreenAdjustmentHeight: height) self.doSet(
windowTopLeftPoint: windowTopLeftPoint, bottomOutOfScreenAdjustmentHeight: height)
} }
} }
@ -124,10 +136,9 @@ public class CandidateController: 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
} }

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
fileprivate class HorizontalCandidateView: NSView { private class HorizontalCandidateView: NSView {
var highlightedIndex: UInt = 0 var highlightedIndex: UInt = 0
var action: Selector? var action: Selector?
weak var target: AnyObject? weak var target: AnyObject?
@ -57,12 +64,14 @@ fileprivate class HorizontalCandidateView: NSView {
let count = min(labels.count, candidates.count) let count = min(labels.count, candidates.count)
keyLabels = Array(labels[0..<count]) keyLabels = Array(labels[0..<count])
displayedCandidates = Array(candidates[0..<count]) displayedCandidates = Array(candidates[0..<count])
dispCandidatesWithLabels = zip(keyLabels,displayedCandidates).map() {$0 + $1} dispCandidatesWithLabels = zip(keyLabels, displayedCandidates).map { $0 + $1 }
var newWidths = [CGFloat]() var newWidths = [CGFloat]()
let baseSize = NSSize(width: 10240.0, height: 10240.0) let baseSize = NSSize(width: 10240.0, height: 10240.0)
for index in 0..<count { for index in 0..<count {
let rctCandidate = (dispCandidatesWithLabels[index] as NSString).boundingRect(with: baseSize, options: .usesLineFragmentOrigin, attributes: candidateWithLabelAttrDict) let rctCandidate = (dispCandidatesWithLabels[index] as NSString).boundingRect(
with: baseSize, options: .usesLineFragmentOrigin,
attributes: candidateWithLabelAttrDict)
var cellWidth = rctCandidate.size.width + cellPadding var cellWidth = rctCandidate.size.width + cellPadding
let cellHeight = rctCandidate.size.height + cellPadding let cellHeight = rctCandidate.size.height + cellPadding
if cellWidth < cellHeight * 1.35 { if cellWidth < cellHeight * 1.35 {
@ -79,17 +88,24 @@ fileprivate class HorizontalCandidateView: NSView {
paraStyle.setParagraphStyle(NSParagraphStyle.default) paraStyle.setParagraphStyle(NSParagraphStyle.default)
paraStyle.alignment = .center paraStyle.alignment = .center
candidateWithLabelAttrDict = [.font: candidateFont, candidateWithLabelAttrDict = [
.font: candidateFont,
.paragraphStyle: paraStyle, .paragraphStyle: paraStyle,
.foregroundColor: NSColor.labelColor] // We still need this dummy section to make sure the space occupations of the candidates are correct. .foregroundColor: NSColor.labelColor,
] // We still need this dummy section to make sure that
// the space occupations of the candidates are correct.
keyLabelAttrDict = [.font: labelFont, keyLabelAttrDict = [
.font: labelFont,
.paragraphStyle: paraStyle, .paragraphStyle: paraStyle,
.verticalGlyphForm: true as AnyObject, .verticalGlyphForm: true as AnyObject,
.foregroundColor: NSColor.secondaryLabelColor] // Candidate phrase text color .foregroundColor: NSColor.secondaryLabelColor,
candidateAttrDict = [.font: candidateFont, ] // Candidate phrase text color
candidateAttrDict = [
.font: candidateFont,
.paragraphStyle: paraStyle, .paragraphStyle: paraStyle,
.foregroundColor: NSColor.labelColor] // Candidate index text color .foregroundColor: NSColor.labelColor,
] // Candidate index text color
let labelFontSize = labelFont.pointSize let labelFontSize = labelFont.pointSize
let candidateFontSize = candidateFont.pointSize let candidateFontSize = candidateFont.pointSize
let biggestSize = max(labelFontSize, candidateFontSize) let biggestSize = max(labelFontSize, candidateFontSize)
@ -106,14 +122,23 @@ fileprivate class HorizontalCandidateView: NSView {
NSColor.systemGray.withAlphaComponent(0.75).setStroke() 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)) 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 var accuWidth: CGFloat = 0
for index in 0..<elementWidths.count { for index in 0..<elementWidths.count {
let currentWidth = elementWidths[index] let currentWidth = elementWidths[index]
let rctCandidateArea = NSRect(x: accuWidth, y: 0.0, width: currentWidth + 1.0, height: candidateTextHeight + cellPadding) let rctCandidateArea = NSRect(
let rctLabel = NSRect(x: accuWidth + cellPadding / 2 - 1, y: cellPadding / 2 , width: keyLabelWidth, height: keyLabelHeight * 2.0) x: accuWidth, y: 0.0, width: currentWidth + 1.0,
let rctCandidatePhrase = NSRect(x: accuWidth + keyLabelWidth - 1, y: cellPadding / 2 , width: currentWidth - keyLabelWidth, height: candidateTextHeight) 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 activeCandidateIndexAttr = keyLabelAttrDict
var activeCandidateAttr = candidateAttrDict var activeCandidateAttr = candidateAttrDict
@ -122,28 +147,43 @@ fileprivate class HorizontalCandidateView: NSView {
// The background color of the highlightened candidate // The background color of the highlightened candidate
switch ctlInputMethod.currentKeyHandler.inputMode { switch ctlInputMethod.currentKeyHandler.inputMode {
case InputMode.imeModeCHS: case InputMode.imeModeCHS:
NSColor.systemRed.blended(withFraction: colorBlendAmount, of: NSColor.controlBackgroundColor)!.setFill() NSColor.systemRed.blended(
withFraction: colorBlendAmount,
of: NSColor.controlBackgroundColor)!
.setFill()
case InputMode.imeModeCHT: case InputMode.imeModeCHT:
NSColor.systemBlue.blended(withFraction: colorBlendAmount, of: NSColor.controlBackgroundColor)!.setFill() NSColor.systemBlue.blended(
withFraction: colorBlendAmount,
of: NSColor.controlBackgroundColor)!
.setFill()
default: default:
NSColor.alternateSelectedControlColor.setFill() NSColor.alternateSelectedControlColor.setFill()
} }
activeCandidateIndexAttr[.foregroundColor] = NSColor.selectedMenuItemTextColor.withAlphaComponent(0.84) // The index text color of the highlightened candidate // Highlightened index text color
activeCandidateAttr[.foregroundColor] = NSColor.selectedMenuItemTextColor // The phrase text color of the highlightened candidate activeCandidateIndexAttr[.foregroundColor] = NSColor.selectedMenuItemTextColor
.withAlphaComponent(0.84)
// Highlightened phrase text color
activeCandidateAttr[.foregroundColor] = NSColor.selectedMenuItemTextColor
} else { } else {
NSColor.controlBackgroundColor.setFill() NSColor.controlBackgroundColor.setFill()
} }
switch ctlInputMethod.currentKeyHandler.inputMode { switch ctlInputMethod.currentKeyHandler.inputMode {
case InputMode.imeModeCHS: case InputMode.imeModeCHS:
if #available(macOS 12.0, *) {activeCandidateAttr[.languageIdentifier] = "zh-Hans" as AnyObject} if #available(macOS 12.0, *) {
activeCandidateAttr[.languageIdentifier] = "zh-Hans" as AnyObject
}
case InputMode.imeModeCHT: case InputMode.imeModeCHT:
if #available(macOS 12.0, *) {activeCandidateAttr[.languageIdentifier] = "zh-Hant" as AnyObject} if #available(macOS 12.0, *) {
activeCandidateAttr[.languageIdentifier] = "zh-Hant" as AnyObject
}
default: default:
break break
} }
NSBezierPath.fill(rctCandidateArea) NSBezierPath.fill(rctCandidateArea)
(keyLabels[index] as NSString).draw(in: rctLabel, withAttributes: activeCandidateIndexAttr) (keyLabels[index] as NSString).draw(
(displayedCandidates[index] as NSString).draw(in: rctCandidatePhrase, withAttributes: activeCandidateAttr) in: rctLabel, withAttributes: activeCandidateIndexAttr)
(displayedCandidates[index] as NSString).draw(
in: rctCandidatePhrase, withAttributes: activeCandidateAttr)
accuWidth += currentWidth + 1.0 accuWidth += currentWidth + 1.0
} }
} }
@ -206,7 +246,8 @@ public class HorizontalCandidateController: CandidateController {
public init() { public init() {
var contentRect = NSRect(x: 128.0, y: 128.0, width: 0.0, height: 0.0) var contentRect = NSRect(x: 128.0, y: 128.0, width: 0.0, height: 0.0)
let styleMask: NSWindow.StyleMask = [.nonactivatingPanel] let styleMask: NSWindow.StyleMask = [.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
panel.isOpaque = false panel.isOpaque = false
@ -216,7 +257,8 @@ public class HorizontalCandidateController: CandidateController {
candidateView = HorizontalCandidateView(frame: contentRect) candidateView = HorizontalCandidateView(frame: contentRect)
candidateView.wantsLayer = true candidateView.wantsLayer = true
candidateView.layer?.borderColor = NSColor.selectedMenuItemTextColor.withAlphaComponent(0.30).cgColor candidateView.layer?.borderColor =
NSColor.selectedMenuItemTextColor.withAlphaComponent(0.30).cgColor
candidateView.layer?.borderWidth = 1.0 candidateView.layer?.borderWidth = 1.0
if #available(macOS 10.13, *) { if #available(macOS 10.13, *) {
candidateView.layer?.cornerRadius = 6.0 candidateView.layer?.cornerRadius = 6.0
@ -225,7 +267,7 @@ public class HorizontalCandidateController: CandidateController {
panel.contentView?.addSubview(candidateView) panel.contentView?.addSubview(candidateView)
contentRect.size = NSSize(width: 20.0, height: 10.0) // Reduce the button width contentRect.size = NSSize(width: 20.0, height: 10.0) // Reduce the button width
let buttonAttribute: [NSAttributedString.Key : Any] = [.font : NSFont.systemFont(ofSize: 9.0)] let buttonAttribute: [NSAttributedString.Key: Any] = [.font: NSFont.systemFont(ofSize: 9.0)]
nextPageButton = NSButton(frame: contentRect) nextPageButton = NSButton(frame: contentRect)
NSColor.controlBackgroundColor.setFill() NSColor.controlBackgroundColor.setFill()
@ -237,7 +279,8 @@ public class HorizontalCandidateController: CandidateController {
nextPageButton.setButtonType(.momentaryLight) nextPageButton.setButtonType(.momentaryLight)
nextPageButton.bezelStyle = .disclosure nextPageButton.bezelStyle = .disclosure
nextPageButton.userInterfaceLayoutDirection = .leftToRight nextPageButton.userInterfaceLayoutDirection = .leftToRight
nextPageButton.attributedTitle = NSMutableAttributedString(string: " ", attributes: buttonAttribute) // Next Page Arrow nextPageButton.attributedTitle = NSMutableAttributedString(
string: " ", attributes: buttonAttribute) // Next Page Arrow
prevPageButton = NSButton(frame: contentRect) prevPageButton = NSButton(frame: contentRect)
NSColor.controlBackgroundColor.setFill() NSColor.controlBackgroundColor.setFill()
NSBezierPath.fill(prevPageButton.bounds) NSBezierPath.fill(prevPageButton.bounds)
@ -248,7 +291,8 @@ public class HorizontalCandidateController: CandidateController {
prevPageButton.setButtonType(.momentaryLight) prevPageButton.setButtonType(.momentaryLight)
prevPageButton.bezelStyle = .disclosure prevPageButton.bezelStyle = .disclosure
prevPageButton.userInterfaceLayoutDirection = .rightToLeft prevPageButton.userInterfaceLayoutDirection = .rightToLeft
prevPageButton.attributedTitle = NSMutableAttributedString(string: " ", attributes: buttonAttribute) // Previous Page Arrow prevPageButton.attributedTitle = NSMutableAttributedString(
string: " ", attributes: buttonAttribute) // Previous Page Arrow
panel.contentView?.addSubview(nextPageButton) panel.contentView?.addSubview(nextPageButton)
panel.contentView?.addSubview(prevPageButton) panel.contentView?.addSubview(prevPageButton)
@ -275,8 +319,8 @@ public class HorizontalCandidateController: CandidateController {
} }
public override func showNextPage() -> Bool { public override func showNextPage() -> Bool {
guard delegate != nil else {return false} guard delegate != nil else { return false }
if pageCount == 1 {return highlightNextCandidate()} if pageCount == 1 { return highlightNextCandidate() }
currentPage = (currentPage + 1 >= pageCount) ? 0 : currentPage + 1 currentPage = (currentPage + 1 >= pageCount) ? 0 : currentPage + 1
candidateView.highlightedIndex = 0 candidateView.highlightedIndex = 0
layoutCandidateView() layoutCandidateView()
@ -284,8 +328,8 @@ public class HorizontalCandidateController: CandidateController {
} }
public override func showPreviousPage() -> Bool { public override func showPreviousPage() -> Bool {
guard delegate != nil else {return false} guard delegate != nil else { return false }
if pageCount == 1 {return highlightPreviousCandidate()} if pageCount == 1 { return highlightPreviousCandidate() }
currentPage = (currentPage == 0) ? pageCount - 1 : currentPage - 1 currentPage = (currentPage == 0) ? pageCount - 1 : currentPage - 1
candidateView.highlightedIndex = 0 candidateView.highlightedIndex = 0
layoutCandidateView() layoutCandidateView()
@ -293,14 +337,18 @@ public class HorizontalCandidateController: CandidateController {
} }
public override func highlightNextCandidate() -> Bool { public override func highlightNextCandidate() -> Bool {
guard let delegate = delegate else {return false} guard let delegate = delegate else { return false }
selectedCandidateIndex = (selectedCandidateIndex + 1 >= delegate.candidateCountForController(self)) ? 0 : selectedCandidateIndex + 1 selectedCandidateIndex =
(selectedCandidateIndex + 1 >= delegate.candidateCountForController(self))
? 0 : selectedCandidateIndex + 1
return true return true
} }
public override func highlightPreviousCandidate() -> Bool { public override func highlightPreviousCandidate() -> Bool {
guard let delegate = delegate else {return false} guard let delegate = delegate else { return false }
selectedCandidateIndex = (selectedCandidateIndex == 0) ? delegate.candidateCountForController(self) - 1 : selectedCandidateIndex - 1 selectedCandidateIndex =
(selectedCandidateIndex == 0)
? delegate.candidateCountForController(self) - 1 : selectedCandidateIndex - 1
return true return true
} }
@ -357,7 +405,8 @@ extension HorizontalCandidateController {
let candidate = delegate.candidateController(self, candidateAtIndex: index) let candidate = delegate.candidateController(self, candidateAtIndex: index)
candidates.append(candidate) candidates.append(candidate)
} }
candidateView.set(keyLabels: keyLabels.map { $0.displayedText}, displayedCandidates: candidates) candidateView.set(
keyLabels: keyLabels.map { $0.displayedText }, displayedCandidates: candidates)
var newSize = candidateView.sizeForView var newSize = candidateView.sizeForView
var frameRect = candidateView.frame var frameRect = candidateView.frame
frameRect.size = newSize frameRect.size = newSize
@ -365,7 +414,7 @@ extension HorizontalCandidateController {
if pageCount > 1 && mgrPrefs.showPageButtonsInCandidateWindow { if pageCount > 1 && mgrPrefs.showPageButtonsInCandidateWindow {
var buttonRect = nextPageButton.frame var buttonRect = nextPageButton.frame
let spacing:CGFloat = 0.0 let spacing: CGFloat = 0.0
buttonRect.size.height = floor(newSize.height / 2) buttonRect.size.height = floor(newSize.height / 2)
@ -373,7 +422,8 @@ extension HorizontalCandidateController {
buttonRect.origin = NSPoint(x: newSize.width, y: buttonOriginY) buttonRect.origin = NSPoint(x: newSize.width, y: buttonOriginY)
nextPageButton.frame = buttonRect nextPageButton.frame = buttonRect
buttonRect.origin = NSPoint(x: newSize.width, y: buttonOriginY + buttonRect.size.height + spacing) buttonRect.origin = NSPoint(
x: newSize.width, y: buttonOriginY + buttonRect.size.height + spacing)
prevPageButton.frame = buttonRect prevPageButton.frame = buttonRect
newSize.width += 20 newSize.width += 20
@ -386,7 +436,8 @@ extension HorizontalCandidateController {
frameRect = window?.frame ?? NSRect.zero frameRect = window?.frame ?? NSRect.zero
let topLeftPoint = NSMakePoint(frameRect.origin.x, frameRect.origin.y + frameRect.size.height) let topLeftPoint = NSMakePoint(
frameRect.origin.x, frameRect.origin.y + frameRect.size.height)
frameRect.size = newSize frameRect.size = newSize
frameRect.origin = NSMakePoint(topLeftPoint.x, topLeftPoint.y - frameRect.size.height) frameRect.origin = NSMakePoint(topLeftPoint.x, topLeftPoint.y - frameRect.size.height)
self.window?.setFrame(frameRect, display: false) self.window?.setFrame(frameRect, display: false)

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
fileprivate class VerticalCandidateView: NSView { private class VerticalCandidateView: NSView {
var highlightedIndex: UInt = 0 var highlightedIndex: UInt = 0
var action: Selector? var action: Selector?
weak var target: AnyObject? weak var target: AnyObject?
@ -58,17 +65,19 @@ fileprivate class VerticalCandidateView: NSView {
let count = min(labels.count, candidates.count) let count = min(labels.count, candidates.count)
keyLabels = Array(labels[0..<count]) keyLabels = Array(labels[0..<count])
displayedCandidates = Array(candidates[0..<count]) displayedCandidates = Array(candidates[0..<count])
dispCandidatesWithLabels = zip(keyLabels,displayedCandidates).map() {$0 + $1} dispCandidatesWithLabels = zip(keyLabels, displayedCandidates).map { $0 + $1 }
var newWidths = [CGFloat]() var newWidths = [CGFloat]()
var calculatedWindowWidth = CGFloat() var calculatedWindowWidth = CGFloat()
var newHeights = [CGFloat]() var newHeights = [CGFloat]()
let baseSize = NSSize(width: 10240.0, height: 10240.0) let baseSize = NSSize(width: 10240.0, height: 10240.0)
for index in 0..<count { for index in 0..<count {
let rctCandidate = (dispCandidatesWithLabels[index] as NSString).boundingRect(with: baseSize, options: .usesLineFragmentOrigin, attributes: candidateWithLabelAttrDict) let rctCandidate = (dispCandidatesWithLabels[index] as NSString).boundingRect(
with: baseSize, options: .usesLineFragmentOrigin,
attributes: candidateWithLabelAttrDict)
let cellWidth = rctCandidate.size.width + cellPadding let cellWidth = rctCandidate.size.width + cellPadding
let cellHeight = rctCandidate.size.height + cellPadding let cellHeight = rctCandidate.size.height + cellPadding
if (calculatedWindowWidth < rctCandidate.size.width) { if calculatedWindowWidth < rctCandidate.size.width {
calculatedWindowWidth = rctCandidate.size.width + cellPadding calculatedWindowWidth = rctCandidate.size.width + cellPadding
} }
newWidths.append(cellWidth) newWidths.append(cellWidth)
@ -76,7 +85,7 @@ fileprivate class VerticalCandidateView: NSView {
} }
elementWidths = newWidths elementWidths = newWidths
elementHeights = newHeights elementHeights = newHeights
windowWidth = calculatedWindowWidth + cellPadding; windowWidth = calculatedWindowWidth + cellPadding
} }
@objc(setKeyLabelFont:candidateFont:) @objc(setKeyLabelFont:candidateFont:)
@ -85,17 +94,24 @@ fileprivate class VerticalCandidateView: NSView {
paraStyle.setParagraphStyle(NSParagraphStyle.default) paraStyle.setParagraphStyle(NSParagraphStyle.default)
paraStyle.alignment = .left paraStyle.alignment = .left
candidateWithLabelAttrDict = [.font: candidateFont, candidateWithLabelAttrDict = [
.font: candidateFont,
.paragraphStyle: paraStyle, .paragraphStyle: paraStyle,
.foregroundColor: NSColor.labelColor] // We still need this dummy section to make sure the space occupations of the candidates are correct. .foregroundColor: NSColor.labelColor,
] // We still need this dummy section to make sure that
// the space occupations of the candidates are correct.
keyLabelAttrDict = [.font: labelFont, keyLabelAttrDict = [
.font: labelFont,
.paragraphStyle: paraStyle, .paragraphStyle: paraStyle,
.verticalGlyphForm: true as AnyObject, .verticalGlyphForm: true as AnyObject,
.foregroundColor: NSColor.secondaryLabelColor] // Candidate phrase text color .foregroundColor: NSColor.secondaryLabelColor,
candidateAttrDict = [.font: candidateFont, ] // Candidate phrase text color
candidateAttrDict = [
.font: candidateFont,
.paragraphStyle: paraStyle, .paragraphStyle: paraStyle,
.foregroundColor: NSColor.labelColor] // Candidate index text color .foregroundColor: NSColor.labelColor,
] // Candidate index text color
let labelFontSize = labelFont.pointSize let labelFontSize = labelFont.pointSize
let candidateFontSize = candidateFont.pointSize let candidateFontSize = candidateFont.pointSize
let biggestSize = max(labelFontSize, candidateFontSize) let biggestSize = max(labelFontSize, candidateFontSize)
@ -112,14 +128,22 @@ fileprivate class VerticalCandidateView: NSView {
NSColor.systemGray.withAlphaComponent(0.75).setStroke() 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)) 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 var accuHeight: CGFloat = 0
for index in 0..<elementHeights.count { for index in 0..<elementHeights.count {
let currentHeight = elementHeights[index] let currentHeight = elementHeights[index]
let rctCandidateArea = NSRect(x: 0.0, y: accuHeight, width: windowWidth, height: candidateTextHeight + cellPadding) let rctCandidateArea = NSRect(
let rctLabel = NSRect(x: cellPadding / 2 - 1, y: accuHeight + cellPadding / 2, width: keyLabelWidth, height: keyLabelHeight * 2.0) x: 0.0, y: accuHeight, width: windowWidth, height: candidateTextHeight + cellPadding
let rctCandidatePhrase = NSRect(x: cellPadding / 2 - 1 + keyLabelWidth, y: accuHeight + cellPadding / 2 - 1, width: windowWidth - keyLabelWidth, height: candidateTextHeight) )
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 activeCandidateIndexAttr = keyLabelAttrDict
var activeCandidateAttr = candidateAttrDict var activeCandidateAttr = candidateAttrDict
@ -128,28 +152,43 @@ fileprivate class VerticalCandidateView: NSView {
// The background color of the highlightened candidate // The background color of the highlightened candidate
switch ctlInputMethod.currentKeyHandler.inputMode { switch ctlInputMethod.currentKeyHandler.inputMode {
case InputMode.imeModeCHS: case InputMode.imeModeCHS:
NSColor.systemRed.blended(withFraction: colorBlendAmount, of: NSColor.controlBackgroundColor)!.setFill() NSColor.systemRed.blended(
withFraction: colorBlendAmount,
of: NSColor.controlBackgroundColor)!
.setFill()
case InputMode.imeModeCHT: case InputMode.imeModeCHT:
NSColor.systemBlue.blended(withFraction: colorBlendAmount, of: NSColor.controlBackgroundColor)!.setFill() NSColor.systemBlue.blended(
withFraction: colorBlendAmount,
of: NSColor.controlBackgroundColor)!
.setFill()
default: default:
NSColor.alternateSelectedControlColor.setFill() NSColor.alternateSelectedControlColor.setFill()
} }
activeCandidateIndexAttr[.foregroundColor] = NSColor.selectedMenuItemTextColor.withAlphaComponent(0.84) // The index text color of the highlightened candidate // Highlightened index text color
activeCandidateAttr[.foregroundColor] = NSColor.selectedMenuItemTextColor // The phrase text color of the highlightened candidate activeCandidateIndexAttr[.foregroundColor] = NSColor.selectedMenuItemTextColor
.withAlphaComponent(0.84)
// Highlightened phrase text color
activeCandidateAttr[.foregroundColor] = NSColor.selectedMenuItemTextColor
} else { } else {
NSColor.controlBackgroundColor.setFill() NSColor.controlBackgroundColor.setFill()
} }
switch ctlInputMethod.currentKeyHandler.inputMode { switch ctlInputMethod.currentKeyHandler.inputMode {
case InputMode.imeModeCHS: case InputMode.imeModeCHS:
if #available(macOS 12.0, *) {activeCandidateAttr[.languageIdentifier] = "zh-Hans" as AnyObject} if #available(macOS 12.0, *) {
activeCandidateAttr[.languageIdentifier] = "zh-Hans" as AnyObject
}
case InputMode.imeModeCHT: case InputMode.imeModeCHT:
if #available(macOS 12.0, *) {activeCandidateAttr[.languageIdentifier] = "zh-Hant" as AnyObject} if #available(macOS 12.0, *) {
activeCandidateAttr[.languageIdentifier] = "zh-Hant" as AnyObject
}
default: default:
break break
} }
NSBezierPath.fill(rctCandidateArea) NSBezierPath.fill(rctCandidateArea)
(keyLabels[index] as NSString).draw(in: rctLabel, withAttributes: activeCandidateIndexAttr) (keyLabels[index] as NSString).draw(
(displayedCandidates[index] as NSString).draw(in: rctCandidatePhrase, withAttributes: activeCandidateAttr) in: rctLabel, withAttributes: activeCandidateIndexAttr)
(displayedCandidates[index] as NSString).draw(
in: rctCandidatePhrase, withAttributes: activeCandidateAttr)
accuHeight += currentHeight accuHeight += currentHeight
} }
} }
@ -212,7 +251,8 @@ public class VerticalCandidateController: CandidateController {
public init() { public init() {
var contentRect = NSRect(x: 128.0, y: 128.0, width: 0.0, height: 0.0) var contentRect = NSRect(x: 128.0, y: 128.0, width: 0.0, height: 0.0)
let styleMask: NSWindow.StyleMask = [.nonactivatingPanel] let styleMask: NSWindow.StyleMask = [.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
panel.isOpaque = false panel.isOpaque = false
@ -222,7 +262,8 @@ public class VerticalCandidateController: CandidateController {
candidateView = VerticalCandidateView(frame: contentRect) candidateView = VerticalCandidateView(frame: contentRect)
candidateView.wantsLayer = true candidateView.wantsLayer = true
candidateView.layer?.borderColor = NSColor.selectedMenuItemTextColor.withAlphaComponent(0.30).cgColor candidateView.layer?.borderColor =
NSColor.selectedMenuItemTextColor.withAlphaComponent(0.30).cgColor
candidateView.layer?.borderWidth = 1.0 candidateView.layer?.borderWidth = 1.0
if #available(macOS 10.13, *) { if #available(macOS 10.13, *) {
candidateView.layer?.cornerRadius = 6.0 candidateView.layer?.cornerRadius = 6.0
@ -231,7 +272,7 @@ public class VerticalCandidateController: CandidateController {
panel.contentView?.addSubview(candidateView) panel.contentView?.addSubview(candidateView)
contentRect.size = NSSize(width: 20.0, height: 10.0) // Reduce the button width contentRect.size = NSSize(width: 20.0, height: 10.0) // Reduce the button width
let buttonAttribute: [NSAttributedString.Key : Any] = [.font : NSFont.systemFont(ofSize: 9.0)] let buttonAttribute: [NSAttributedString.Key: Any] = [.font: NSFont.systemFont(ofSize: 9.0)]
nextPageButton = NSButton(frame: contentRect) nextPageButton = NSButton(frame: contentRect)
NSColor.controlBackgroundColor.setFill() NSColor.controlBackgroundColor.setFill()
@ -243,7 +284,8 @@ public class VerticalCandidateController: CandidateController {
nextPageButton.setButtonType(.momentaryLight) nextPageButton.setButtonType(.momentaryLight)
nextPageButton.bezelStyle = .disclosure nextPageButton.bezelStyle = .disclosure
nextPageButton.userInterfaceLayoutDirection = .leftToRight nextPageButton.userInterfaceLayoutDirection = .leftToRight
nextPageButton.attributedTitle = NSMutableAttributedString(string: " ", attributes: buttonAttribute) // Next Page Arrow nextPageButton.attributedTitle = NSMutableAttributedString(
string: " ", attributes: buttonAttribute) // Next Page Arrow
prevPageButton = NSButton(frame: contentRect) prevPageButton = NSButton(frame: contentRect)
NSColor.controlBackgroundColor.setFill() NSColor.controlBackgroundColor.setFill()
NSBezierPath.fill(prevPageButton.bounds) NSBezierPath.fill(prevPageButton.bounds)
@ -254,7 +296,8 @@ public class VerticalCandidateController: CandidateController {
prevPageButton.setButtonType(.momentaryLight) prevPageButton.setButtonType(.momentaryLight)
prevPageButton.bezelStyle = .disclosure prevPageButton.bezelStyle = .disclosure
prevPageButton.userInterfaceLayoutDirection = .rightToLeft prevPageButton.userInterfaceLayoutDirection = .rightToLeft
prevPageButton.attributedTitle = NSMutableAttributedString(string: " ", attributes: buttonAttribute) // Previous Page Arrow prevPageButton.attributedTitle = NSMutableAttributedString(
string: " ", attributes: buttonAttribute) // Previous Page Arrow
panel.contentView?.addSubview(nextPageButton) panel.contentView?.addSubview(nextPageButton)
panel.contentView?.addSubview(prevPageButton) panel.contentView?.addSubview(prevPageButton)
@ -281,8 +324,8 @@ public class VerticalCandidateController: CandidateController {
} }
public override func showNextPage() -> Bool { public override func showNextPage() -> Bool {
guard delegate != nil else {return false} guard delegate != nil else { return false }
if pageCount == 1 {return highlightNextCandidate()} if pageCount == 1 { return highlightNextCandidate() }
currentPage = (currentPage + 1 >= pageCount) ? 0 : currentPage + 1 currentPage = (currentPage + 1 >= pageCount) ? 0 : currentPage + 1
candidateView.highlightedIndex = 0 candidateView.highlightedIndex = 0
layoutCandidateView() layoutCandidateView()
@ -290,8 +333,8 @@ public class VerticalCandidateController: CandidateController {
} }
public override func showPreviousPage() -> Bool { public override func showPreviousPage() -> Bool {
guard delegate != nil else {return false} guard delegate != nil else { return false }
if pageCount == 1 {return highlightPreviousCandidate()} if pageCount == 1 { return highlightPreviousCandidate() }
currentPage = (currentPage == 0) ? pageCount - 1 : currentPage - 1 currentPage = (currentPage == 0) ? pageCount - 1 : currentPage - 1
candidateView.highlightedIndex = 0 candidateView.highlightedIndex = 0
layoutCandidateView() layoutCandidateView()
@ -299,14 +342,18 @@ public class VerticalCandidateController: CandidateController {
} }
public override func highlightNextCandidate() -> Bool { public override func highlightNextCandidate() -> Bool {
guard let delegate = delegate else {return false} guard let delegate = delegate else { return false }
selectedCandidateIndex = (selectedCandidateIndex + 1 >= delegate.candidateCountForController(self)) ? 0 : selectedCandidateIndex + 1 selectedCandidateIndex =
(selectedCandidateIndex + 1 >= delegate.candidateCountForController(self))
? 0 : selectedCandidateIndex + 1
return true return true
} }
public override func highlightPreviousCandidate() -> Bool { public override func highlightPreviousCandidate() -> Bool {
guard let delegate = delegate else {return false} guard let delegate = delegate else { return false }
selectedCandidateIndex = (selectedCandidateIndex == 0) ? delegate.candidateCountForController(self) - 1 : selectedCandidateIndex - 1 selectedCandidateIndex =
(selectedCandidateIndex == 0)
? delegate.candidateCountForController(self) - 1 : selectedCandidateIndex - 1
return true return true
} }
@ -363,7 +410,8 @@ extension VerticalCandidateController {
let candidate = delegate.candidateController(self, candidateAtIndex: index) let candidate = delegate.candidateController(self, candidateAtIndex: index)
candidates.append(candidate) candidates.append(candidate)
} }
candidateView.set(keyLabels: keyLabels.map { $0.displayedText}, displayedCandidates: candidates) candidateView.set(
keyLabels: keyLabels.map { $0.displayedText }, displayedCandidates: candidates)
var newSize = candidateView.sizeForView var newSize = candidateView.sizeForView
var frameRect = candidateView.frame var frameRect = candidateView.frame
frameRect.size = newSize frameRect.size = newSize
@ -371,7 +419,7 @@ extension VerticalCandidateController {
if pageCount > 1 && mgrPrefs.showPageButtonsInCandidateWindow { if pageCount > 1 && mgrPrefs.showPageButtonsInCandidateWindow {
var buttonRect = nextPageButton.frame var buttonRect = nextPageButton.frame
let spacing:CGFloat = 0.0 let spacing: CGFloat = 0.0
// buttonRect.size.height = floor(candidateTextHeight + cellPadding / 2) // buttonRect.size.height = floor(candidateTextHeight + cellPadding / 2)
@ -379,7 +427,8 @@ extension VerticalCandidateController {
buttonRect.origin = NSPoint(x: newSize.width, y: buttonOriginY) buttonRect.origin = NSPoint(x: newSize.width, y: buttonOriginY)
nextPageButton.frame = buttonRect nextPageButton.frame = buttonRect
buttonRect.origin = NSPoint(x: newSize.width, y: buttonOriginY + buttonRect.size.height + spacing) buttonRect.origin = NSPoint(
x: newSize.width, y: buttonOriginY + buttonRect.size.height + spacing)
prevPageButton.frame = buttonRect prevPageButton.frame = buttonRect
newSize.width += 20 newSize.width += 20
@ -392,7 +441,8 @@ extension VerticalCandidateController {
frameRect = window?.frame ?? NSRect.zero frameRect = window?.frame ?? NSRect.zero
let topLeftPoint = NSMakePoint(frameRect.origin.x, frameRect.origin.y + frameRect.size.height) let topLeftPoint = NSMakePoint(
frameRect.origin.x, frameRect.origin.y + frameRect.size.height)
frameRect.size = newSize frameRect.size = newSize
frameRect.origin = NSMakePoint(topLeftPoint.x, topLeftPoint.y - frameRect.size.height) frameRect.origin = NSMakePoint(topLeftPoint.x, topLeftPoint.y - frameRect.size.height)
self.window?.setFrame(frameRect, display: false) self.window?.setFrame(frameRect, display: false)

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,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.
*/ */
import Cocoa
import Carbon import Carbon
import Cocoa
// Extend the RangeReplaceableCollection to allow it clean duplicated characters. // Extend the RangeReplaceableCollection to allow it clean duplicated characters.
extension RangeReplaceableCollection where Element: Hashable { extension RangeReplaceableCollection where Element: Hashable {
var charDeDuplicate: Self { var charDeDuplicate: Self {
var set = Set<Element>() var set = Set<Element>()
return filter{ set.insert($0).inserted } return filter { set.insert($0).inserted }
} }
} }
@ -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

@ -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
@ -31,12 +37,12 @@ class AppDelegate: NSObject, NSApplicationDelegate {
// Insert code here to tear down your application // Insert code here to tear down your application
} }
func applicationShouldTerminate(_ sender: NSApplication)-> NSApplication.TerminateReply { func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {
return .terminateNow return .terminateNow
} }
// 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) {