Swift // Clang-Format.
This commit is contained in:
parent
63c6a3e4be
commit
1fe13c1220
|
@ -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
|
||||||
|
}
|
|
@ -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): 寫入完成。")
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,27 @@
|
||||||
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License).
|
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License).
|
||||||
// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
// All possible vChewing-specific modifications are of:
|
||||||
|
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||||
/*
|
/*
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
|
the Software without restriction, including without limitation the rights to
|
||||||
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
1. The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor,
|
2. No trademark license is granted to use the trade names, trademarks, service
|
||||||
except as required to fulfill notice requirements above.
|
marks, or product names of Contributor, except as required to fulfill notice
|
||||||
|
requirements above.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
|
@ -1,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 {
|
||||||
|
|
|
@ -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 == "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" }
|
||||||
}
|
}
|
||||||
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
|
||||||
|
|
|
@ -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: "﹣﹦≡|∣∥–︱—︳╴¯ ̄﹉﹊﹍﹎﹋﹌﹏︴∕﹨╱╲/\"),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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: "")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"];
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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 = ""
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in New Issue