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).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
|
||||
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
1. The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor,
|
||||
except as required to fulfill notice requirements above.
|
||||
2. No trademark license is granted to use the trade names, trademarks, service
|
||||
marks, or product names of Contributor, except as required to fulfill notice
|
||||
requirements above.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
|
||||
// MARK: - 前導工作
|
||||
fileprivate extension String {
|
||||
mutating func regReplace(pattern: String, replaceWith: String = "") {
|
||||
extension String {
|
||||
fileprivate mutating func regReplace(pattern: String, replaceWith: String = "") {
|
||||
// Ref: https://stackoverflow.com/a/40993403/4162914 && https://stackoverflow.com/a/71291137/4162914
|
||||
do {
|
||||
let regex = try NSRegularExpression(pattern: pattern, options: [.caseInsensitive, .anchorsMatchLines])
|
||||
let regex = try NSRegularExpression(
|
||||
pattern: pattern, options: [.caseInsensitive, .anchorsMatchLines])
|
||||
let range = NSRange(self.startIndex..., in: self)
|
||||
self = regex.stringByReplacingMatches(in: self, options: [], range: range, withTemplate: replaceWith)
|
||||
self = regex.stringByReplacingMatches(
|
||||
in: self, options: [], range: range, withTemplate: replaceWith)
|
||||
} catch { return }
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func getDocumentsDirectory() -> URL {
|
||||
private func getDocumentsDirectory() -> URL {
|
||||
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
|
||||
return paths[0]
|
||||
}
|
||||
|
||||
// MARK: - 引入小數點位數控制函數
|
||||
// Ref: https://stackoverflow.com/a/32581409/4162914
|
||||
fileprivate extension Float {
|
||||
func rounded(toPlaces places:Int) -> Float {
|
||||
extension Float {
|
||||
fileprivate func rounded(toPlaces places: Int) -> Float {
|
||||
let divisor = pow(10.0, Float(places))
|
||||
return (self * divisor).rounded() / divisor
|
||||
}
|
||||
|
@ -51,7 +61,7 @@ precedencegroup ExponentiationPrecedence {
|
|||
higherThan: MultiplicationPrecedence
|
||||
}
|
||||
|
||||
infix operator ** : ExponentiationPrecedence
|
||||
infix operator **: ExponentiationPrecedence
|
||||
|
||||
func ** (_ base: Double, _ exp: Double) -> Double {
|
||||
return pow(base, exp)
|
||||
|
@ -72,35 +82,35 @@ struct Entry {
|
|||
|
||||
// MARK: - 登記全局根常數變數
|
||||
|
||||
fileprivate let urlCurrentFolder = URL(fileURLWithPath: FileManager.default.currentDirectoryPath)
|
||||
private let urlCurrentFolder = URL(fileURLWithPath: FileManager.default.currentDirectoryPath)
|
||||
|
||||
fileprivate let url_CHS_Custom: String = "./components/chs/phrases-custom-chs.txt"
|
||||
fileprivate let url_CHS_MCBP: String = "./components/chs/phrases-mcbp-chs.txt"
|
||||
fileprivate let url_CHS_MOE: String = "./components/chs/phrases-moe-chs.txt"
|
||||
fileprivate let url_CHS_VCHEW: String = "./components/chs/phrases-vchewing-chs.txt"
|
||||
private let urlCHSforCustom: String = "./components/chs/phrases-custom-chs.txt"
|
||||
private let urlCHSforMCBP: String = "./components/chs/phrases-mcbp-chs.txt"
|
||||
private let urlCHSforMOE: String = "./components/chs/phrases-moe-chs.txt"
|
||||
private let urlCHSforVCHEW: String = "./components/chs/phrases-vchewing-chs.txt"
|
||||
|
||||
fileprivate let url_CHT_Custom: String = "./components/cht/phrases-custom-cht.txt"
|
||||
fileprivate let url_CHT_MCBP: String = "./components/cht/phrases-mcbp-cht.txt"
|
||||
fileprivate let url_CHT_MOE: String = "./components/cht/phrases-moe-cht.txt"
|
||||
fileprivate let url_CHT_VCHEW: String = "./components/cht/phrases-vchewing-cht.txt"
|
||||
private let urlCHTforCustom: String = "./components/cht/phrases-custom-cht.txt"
|
||||
private let urlCHTforMCBP: String = "./components/cht/phrases-mcbp-cht.txt"
|
||||
private let urlCHTforMOE: String = "./components/cht/phrases-moe-cht.txt"
|
||||
private let urlCHTforVCHEW: String = "./components/cht/phrases-vchewing-cht.txt"
|
||||
|
||||
fileprivate let urlKanjiCore: String = "./components/common/char-kanji-core.txt"
|
||||
fileprivate let urlPunctuation: String = "./components/common/data-punctuations.txt"
|
||||
fileprivate let urlMiscBPMF: String = "./components/common/char-misc-bpmf.txt"
|
||||
fileprivate let urlMiscNonKanji: String = "./components/common/char-misc-nonkanji.txt"
|
||||
private let urlKanjiCore: String = "./components/common/char-kanji-core.txt"
|
||||
private let urlPunctuation: String = "./components/common/data-punctuations.txt"
|
||||
private let urlMiscBPMF: String = "./components/common/char-misc-bpmf.txt"
|
||||
private let urlMiscNonKanji: String = "./components/common/char-misc-nonkanji.txt"
|
||||
|
||||
fileprivate let urlOutputCHS: String = "./data-chs.txt"
|
||||
fileprivate let urlOutputCHT: String = "./data-cht.txt"
|
||||
private let urlOutputCHS: String = "./data-chs.txt"
|
||||
private let urlOutputCHT: String = "./data-cht.txt"
|
||||
|
||||
// MARK: - 載入詞組檔案且輸出數組
|
||||
|
||||
func rawDictForPhrases(isCHS: Bool) -> [Entry] {
|
||||
var arrEntryRAW: [Entry] = []
|
||||
var strRAW: String = ""
|
||||
let urlCustom: String = isCHS ? url_CHS_Custom : url_CHT_Custom
|
||||
let urlMCBP: String = isCHS ? url_CHS_MCBP : url_CHT_MCBP
|
||||
let urlMOE: String = isCHS ? url_CHS_MOE : url_CHT_MOE
|
||||
let urlVCHEW: String = isCHS ? url_CHS_VCHEW : url_CHT_VCHEW
|
||||
let urlCustom: String = isCHS ? urlCHSforCustom : urlCHTforCustom
|
||||
let urlMCBP: String = isCHS ? urlCHSforMCBP : urlCHTforMCBP
|
||||
let urlMOE: String = isCHS ? urlCHSforMOE : urlCHTforMOE
|
||||
let urlVCHEW: String = isCHS ? urlCHSforVCHEW : urlCHTforVCHEW
|
||||
let i18n: String = isCHS ? "簡體中文" : "繁體中文"
|
||||
// 讀取內容
|
||||
do {
|
||||
|
@ -111,8 +121,7 @@ func rawDictForPhrases(isCHS: Bool) -> [Entry] {
|
|||
strRAW += try String(contentsOfFile: urlMOE, encoding: .utf8)
|
||||
strRAW += "\n"
|
||||
strRAW += try String(contentsOfFile: urlVCHEW, encoding: .utf8)
|
||||
}
|
||||
catch {
|
||||
} catch {
|
||||
NSLog(" - Exception happened when reading raw phrases data.")
|
||||
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: #"^(#.*|.*#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 {
|
||||
// 第三欄開始是注音
|
||||
let arrLineData = lineData.components(separatedBy: " ")
|
||||
|
@ -144,7 +154,7 @@ func rawDictForPhrases(isCHS: Bool) -> [Entry] {
|
|||
}
|
||||
}
|
||||
// 然後直接乾脆就轉成 Entry 吧。
|
||||
let arrCells : [String] = varLineDataProcessed.components(separatedBy: "\t")
|
||||
let arrCells: [String] = varLineDataProcessed.components(separatedBy: "\t")
|
||||
count = 0 // 不需要再定義,因為之前已經有定義過了。
|
||||
var phone = ""
|
||||
var phrase = ""
|
||||
|
@ -159,7 +169,11 @@ func rawDictForPhrases(isCHS: Bool) -> [Entry] {
|
|||
}
|
||||
}
|
||||
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): 成功生成詞語語料辭典(權重待計算)。")
|
||||
|
@ -175,8 +189,7 @@ func rawDictForKanjis(isCHS: Bool) -> [Entry] {
|
|||
// 讀取內容
|
||||
do {
|
||||
strRAW += try String(contentsOfFile: urlKanjiCore, encoding: .utf8)
|
||||
}
|
||||
catch {
|
||||
} catch {
|
||||
NSLog(" - Exception happened when reading raw core kanji data.")
|
||||
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: #"^(#.*|.*#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 = ""
|
||||
for lineData in arrData {
|
||||
// 簡體中文的話,提取 1,2,4;繁體中文的話,提取 1,3,4。
|
||||
let varLineDataPre = lineData.components(separatedBy: " ").prefix(isCHS ? 2 : 1).joined(separator: "\t")
|
||||
let varLineDataPost = lineData.components(separatedBy: " ").suffix(isCHS ? 1 : 2).joined(separator: "\t")
|
||||
let varLineDataPre = lineData.components(separatedBy: " ").prefix(isCHS ? 2 : 1)
|
||||
.joined(
|
||||
separator: "\t")
|
||||
let varLineDataPost = lineData.components(separatedBy: " ").suffix(isCHS ? 1 : 2)
|
||||
.joined(
|
||||
separator: "\t")
|
||||
varLineData = varLineDataPre + "\t" + varLineDataPost
|
||||
let arrLineData = varLineData.components(separatedBy: " ")
|
||||
var varLineDataProcessed: String = ""
|
||||
|
@ -212,7 +230,7 @@ func rawDictForKanjis(isCHS: Bool) -> [Entry] {
|
|||
}
|
||||
}
|
||||
// 然後直接乾脆就轉成 Entry 吧。
|
||||
let arrCells : [String] = varLineDataProcessed.components(separatedBy: "\t")
|
||||
let arrCells: [String] = varLineDataProcessed.components(separatedBy: "\t")
|
||||
count = 0 // 不需要再定義,因為之前已經有定義過了。
|
||||
var phone = ""
|
||||
var phrase = ""
|
||||
|
@ -227,7 +245,11 @@ func rawDictForKanjis(isCHS: Bool) -> [Entry] {
|
|||
}
|
||||
}
|
||||
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): 成功生成單字語料辭典(權重待計算)。")
|
||||
|
@ -245,8 +267,7 @@ func rawDictForNonKanjis(isCHS: Bool) -> [Entry] {
|
|||
strRAW += try String(contentsOfFile: urlMiscBPMF, encoding: .utf8)
|
||||
strRAW += "\n"
|
||||
strRAW += try String(contentsOfFile: urlMiscNonKanji, encoding: .utf8)
|
||||
}
|
||||
catch {
|
||||
} catch {
|
||||
NSLog(" - Exception happened when reading raw core kanji data.")
|
||||
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: #"^(#.*|.*#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 = ""
|
||||
for lineData in arrData {
|
||||
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: " ")
|
||||
var varLineDataProcessed: String = ""
|
||||
var count = 0
|
||||
|
@ -281,7 +304,7 @@ func rawDictForNonKanjis(isCHS: Bool) -> [Entry] {
|
|||
}
|
||||
}
|
||||
// 然後直接乾脆就轉成 Entry 吧。
|
||||
let arrCells : [String] = varLineDataProcessed.components(separatedBy: "\t")
|
||||
let arrCells: [String] = varLineDataProcessed.components(separatedBy: "\t")
|
||||
count = 0 // 不需要再定義,因為之前已經有定義過了。
|
||||
var phone = ""
|
||||
var phrase = ""
|
||||
|
@ -296,7 +319,11 @@ func rawDictForNonKanjis(isCHS: Bool) -> [Entry] {
|
|||
}
|
||||
}
|
||||
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): 成功生成非漢字語料辭典(權重待計算)。")
|
||||
|
@ -310,7 +337,8 @@ func weightAndSort(_ arrStructUncalculated: [Entry], isCHS: Bool) -> [Entry] {
|
|||
var norm: Float = 0.0
|
||||
for entry in arrStructUncalculated {
|
||||
if entry.valCount >= 0 {
|
||||
norm += fscale**(Float(entry.valPhrase.count) / 3.0 - 1.0) * Float(entry.valCount)
|
||||
norm += fscale ** (Float(entry.valPhrase.count) / 3.0 - 1.0)
|
||||
* Float(entry.valCount)
|
||||
}
|
||||
}
|
||||
// norm 計算完畢,開始將 norm 作為新的固定常數來為每個詞條記錄計算權重。
|
||||
|
@ -324,30 +352,39 @@ func weightAndSort(_ arrStructUncalculated: [Entry], isCHS: Bool) -> [Entry] {
|
|||
case -1: // 單個假名
|
||||
weight = -13
|
||||
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:
|
||||
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) // 為了節省生成的檔案體積,僅保留小數點後三位。
|
||||
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): 成功計算權重。")
|
||||
// ==========================================
|
||||
// 接下來是排序,先按照注音遞減排序一遍、再按照權重遞減排序一遍。
|
||||
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): 排序整理完畢,準備編譯要寫入的檔案內容。")
|
||||
return arrStructSorted
|
||||
}
|
||||
|
||||
func fileOutput(isCHS: Bool) {
|
||||
let i18n: String = isCHS ? "簡體中文" : "繁體中文"
|
||||
let pathOutput = urlCurrentFolder.appendingPathComponent(isCHS ? urlOutputCHS : urlOutputCHT)
|
||||
let pathOutput = urlCurrentFolder.appendingPathComponent(
|
||||
isCHS ? urlOutputCHS : urlOutputCHT)
|
||||
var strPrintLine = ""
|
||||
// 讀取標點內容
|
||||
do {
|
||||
strPrintLine += try String(contentsOfFile: urlPunctuation, encoding: .utf8)
|
||||
}
|
||||
catch {
|
||||
} catch {
|
||||
NSLog(" - \(i18n): Exception happened when reading raw punctuation data.")
|
||||
}
|
||||
NSLog(" - \(i18n): 成功插入標點符號與西文字母數據。")
|
||||
|
@ -360,13 +397,14 @@ func fileOutput(isCHS: Bool) {
|
|||
arrStructUnified = weightAndSort(arrStructUnified, isCHS: isCHS)
|
||||
|
||||
for entry in arrStructUnified {
|
||||
strPrintLine += entry.valPhone + " " + entry.valPhrase + " " + String(entry.valWeight) + "\n"
|
||||
strPrintLine +=
|
||||
entry.valPhone + " " + entry.valPhrase + " " + String(entry.valWeight)
|
||||
+ "\n"
|
||||
}
|
||||
NSLog(" - \(i18n): 要寫入檔案的內容編譯完畢。")
|
||||
do {
|
||||
try strPrintLine.write(to: pathOutput, atomically: false, encoding: .utf8)
|
||||
}
|
||||
catch {
|
||||
} catch {
|
||||
NSLog(" - \(i18n): Error on writing strings to file: \(error)")
|
||||
}
|
||||
NSLog(" - \(i18n): 寫入完成。")
|
||||
|
|
|
@ -1,20 +1,27 @@
|
|||
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License).
|
||||
// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
// All possible vChewing-specific modifications are of:
|
||||
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
|
||||
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
1. The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor,
|
||||
except as required to fulfill notice requirements above.
|
||||
2. No trademark license is granted to use the trade names, trademarks, service
|
||||
marks, or product names of Contributor, except as required to fulfill notice
|
||||
requirements above.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
|
@ -23,9 +30,11 @@ private let kTargetBin = "vChewing"
|
|||
private let kTargetType = "app"
|
||||
private let kTargetBundle = "vChewing.app"
|
||||
|
||||
private let urlDestinationPartial = FileManager.default.urls(for: .inputMethodsDirectory, in: .userDomainMask)[0]
|
||||
private let urlDestinationPartial = FileManager.default.urls(
|
||||
for: .inputMethodsDirectory, in: .userDomainMask)[0]
|
||||
private let urlTargetPartial = urlDestinationPartial.appendingPathComponent(kTargetBundle)
|
||||
private let urlTargetFullBinPartial = urlTargetPartial.appendingPathComponent("Contents/MacOS/").appendingPathComponent(kTargetBin)
|
||||
private let urlTargetFullBinPartial = urlTargetPartial.appendingPathComponent("Contents/MacOS/")
|
||||
.appendingPathComponent(kTargetBin)
|
||||
|
||||
private let kDestinationPartial = urlDestinationPartial.path
|
||||
private let kTargetPartialPath = urlTargetPartial.path
|
||||
|
@ -35,7 +44,7 @@ private let kTranslocationRemovalTickInterval: TimeInterval = 0.5
|
|||
private let kTranslocationRemovalDeadline: TimeInterval = 60.0
|
||||
|
||||
@NSApplicationMain
|
||||
@objc (AppDelegate)
|
||||
@objc(AppDelegate)
|
||||
class AppDelegate: NSWindowController, NSApplicationDelegate {
|
||||
@IBOutlet weak private var installButton: NSButton!
|
||||
@IBOutlet weak private var cancelButton: NSButton!
|
||||
|
@ -61,8 +70,11 @@ class AppDelegate: NSWindowController, NSApplicationDelegate {
|
|||
}
|
||||
|
||||
func applicationDidFinishLaunching(_ notification: Notification) {
|
||||
guard let installingVersion = Bundle.main.infoDictionary?[kCFBundleVersionKey as String] as? String,
|
||||
let versionString = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String else {
|
||||
guard
|
||||
let installingVersion = Bundle.main.infoDictionary?[kCFBundleVersionKey as String]
|
||||
as? String,
|
||||
let versionString = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
|
||||
else {
|
||||
return
|
||||
}
|
||||
self.installingVersion = installingVersion
|
||||
|
@ -75,25 +87,36 @@ class AppDelegate: NSWindowController, NSApplicationDelegate {
|
|||
window?.defaultButtonCell = cell
|
||||
}
|
||||
|
||||
if let copyrightLabel = Bundle.main.localizedInfoDictionary?["NSHumanReadableCopyright"] as? String {
|
||||
if let copyrightLabel = Bundle.main.localizedInfoDictionary?["NSHumanReadableCopyright"]
|
||||
as? String
|
||||
{
|
||||
appCopyrightLabel.stringValue = copyrightLabel
|
||||
}
|
||||
if let eulaContent = Bundle.main.localizedInfoDictionary?["CFEULAContent"] as? String {
|
||||
appEULAContent.string = eulaContent
|
||||
}
|
||||
appVersionLabel.stringValue = String(format: "%@ Build %@", versionString, installingVersion)
|
||||
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(.miniaturizeButton)?.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 shortVersion = currentBundle?.infoDictionary?["CFBundleShortVersionString"] as? String
|
||||
let currentVersion = currentBundle?.infoDictionary?[kCFBundleVersionKey as String] as? String
|
||||
let shortVersion =
|
||||
currentBundle?.infoDictionary?["CFBundleShortVersionString"] as? String
|
||||
let currentVersion =
|
||||
currentBundle?.infoDictionary?[kCFBundleVersionKey as String] as? String
|
||||
currentVersionNumber = (currentVersion as NSString?)?.integerValue ?? 0
|
||||
if shortVersion != nil, let currentVersion = currentVersion, currentVersion.compare(installingVersion, options: .numeric) == .orderedAscending {
|
||||
if shortVersion != nil, let currentVersion = currentVersion,
|
||||
currentVersion.compare(installingVersion, options: .numeric) == .orderedAscending
|
||||
{
|
||||
upgrading = true
|
||||
}
|
||||
}
|
||||
|
@ -126,12 +149,18 @@ class AppDelegate: NSWindowController, NSApplicationDelegate {
|
|||
}
|
||||
|
||||
func removeThenInstallInputMethod() {
|
||||
if FileManager.default.fileExists(atPath: (kTargetPartialPath as NSString).expandingTildeInPath) == false {
|
||||
self.installInputMethod(previousExists: false, previousVersionNotFullyDeactivatedWarning: false)
|
||||
if FileManager.default.fileExists(
|
||||
atPath: (kTargetPartialPath as NSString).expandingTildeInPath)
|
||||
== false
|
||||
{
|
||||
self.installInputMethod(
|
||||
previousExists: false, previousVersionNotFullyDeactivatedWarning: false)
|
||||
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 {
|
||||
|
@ -148,8 +177,7 @@ class AppDelegate: NSWindowController, NSApplicationDelegate {
|
|||
NSLog("File does not exist")
|
||||
}
|
||||
|
||||
}
|
||||
catch let error as NSError {
|
||||
} catch let error as NSError {
|
||||
NSLog("An error took place: \(error)")
|
||||
}
|
||||
|
||||
|
@ -164,32 +192,47 @@ class AppDelegate: NSWindowController, NSApplicationDelegate {
|
|||
window?.beginSheet(progressSheet) { returnCode in
|
||||
DispatchQueue.main.async {
|
||||
if returnCode == .continue {
|
||||
self.installInputMethod(previousExists: true, previousVersionNotFullyDeactivatedWarning: false)
|
||||
self.installInputMethod(
|
||||
previousExists: true,
|
||||
previousVersionNotFullyDeactivatedWarning: false)
|
||||
} else {
|
||||
self.installInputMethod(previousExists: true, previousVersionNotFullyDeactivatedWarning: true)
|
||||
self.installInputMethod(
|
||||
previousExists: true,
|
||||
previousVersionNotFullyDeactivatedWarning: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
self.installInputMethod(previousExists: false, previousVersionNotFullyDeactivatedWarning: false)
|
||||
self.installInputMethod(
|
||||
previousExists: false, previousVersionNotFullyDeactivatedWarning: false)
|
||||
}
|
||||
}
|
||||
|
||||
func installInputMethod(previousExists: Bool, previousVersionNotFullyDeactivatedWarning warning: Bool) {
|
||||
guard let targetBundle = archiveUtil?.unzipNotarizedArchive() ?? Bundle.main.path(forResource: kTargetBin, ofType: kTargetType) else {
|
||||
func installInputMethod(
|
||||
previousExists: Bool, previousVersionNotFullyDeactivatedWarning warning: Bool
|
||||
) {
|
||||
guard
|
||||
let targetBundle = archiveUtil?.unzipNotarizedArchive()
|
||||
?? Bundle.main.path(forResource: kTargetBin, ofType: kTargetType)
|
||||
else {
|
||||
return
|
||||
}
|
||||
let cpTask = Process()
|
||||
cpTask.launchPath = "/bin/cp"
|
||||
cpTask.arguments = ["-R", targetBundle, (kDestinationPartial as NSString).expandingTildeInPath]
|
||||
cpTask.arguments = [
|
||||
"-R", targetBundle, (kDestinationPartial as NSString).expandingTildeInPath,
|
||||
]
|
||||
cpTask.launch()
|
||||
cpTask.waitUntilExit()
|
||||
|
||||
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: ""),
|
||||
buttonTitle: NSLocalizedString("Cancel", comment: ""))
|
||||
endAppWithDelay()
|
||||
|
@ -206,28 +249,38 @@ class AppDelegate: NSWindowController, NSApplicationDelegate {
|
|||
var inputSource = InputSourceHelper.inputSource(for: imeIdentifier)
|
||||
|
||||
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)
|
||||
if !status {
|
||||
let message = String(format: NSLocalizedString("Cannot find input source %@ after registration.", comment: ""), imeIdentifier)
|
||||
runAlertPanel(title: NSLocalizedString("Fatal Error", comment: ""), message: message, buttonTitle: NSLocalizedString("Abort", comment: ""))
|
||||
let message = String(
|
||||
format: NSLocalizedString(
|
||||
"Cannot find input source %@ after registration.", comment: ""),
|
||||
imeIdentifier)
|
||||
runAlertPanel(
|
||||
title: NSLocalizedString("Fatal Error", comment: ""), message: message,
|
||||
buttonTitle: NSLocalizedString("Abort", comment: ""))
|
||||
endAppWithDelay()
|
||||
return
|
||||
}
|
||||
|
||||
inputSource = InputSourceHelper.inputSource(for: imeIdentifier)
|
||||
if inputSource == nil {
|
||||
let message = String(format: NSLocalizedString("Cannot find input source %@ after registration.", comment: ""), imeIdentifier)
|
||||
runAlertPanel(title: NSLocalizedString("Fatal Error", comment: ""), message: message, buttonTitle: NSLocalizedString("Abort", comment: ""))
|
||||
let message = String(
|
||||
format: NSLocalizedString(
|
||||
"Cannot find input source %@ after registration.", comment: ""),
|
||||
imeIdentifier)
|
||||
runAlertPanel(
|
||||
title: NSLocalizedString("Fatal Error", comment: ""), message: message,
|
||||
buttonTitle: NSLocalizedString("Abort", comment: ""))
|
||||
}
|
||||
}
|
||||
|
||||
var isMacOS12OrAbove = false
|
||||
if #available(macOS 12.0, *) {
|
||||
NSLog("macOS 12 or later detected.");
|
||||
NSLog("macOS 12 or later detected.")
|
||||
isMacOS12OrAbove = true
|
||||
} 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+,
|
||||
|
@ -238,10 +291,10 @@ class AppDelegate: NSWindowController, NSApplicationDelegate {
|
|||
var mainInputSourceEnabled = InputSourceHelper.inputSourceEnabled(for: inputSource!)
|
||||
if !mainInputSourceEnabled || isMacOS12OrAbove {
|
||||
mainInputSourceEnabled = InputSourceHelper.enable(inputSource: inputSource!)
|
||||
if (mainInputSourceEnabled) {
|
||||
NSLog("Input method enabled: \(imeIdentifier)");
|
||||
if mainInputSourceEnabled {
|
||||
NSLog("Input method enabled: \(imeIdentifier)")
|
||||
} 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()
|
||||
if warning {
|
||||
ntfPostInstall.messageText = NSLocalizedString("Attention", comment: "")
|
||||
ntfPostInstall.informativeText = NSLocalizedString("vChewing is upgraded, but please log out or reboot for the new version to be fully functional.", comment: "")
|
||||
ntfPostInstall.informativeText = NSLocalizedString(
|
||||
"vChewing is upgraded, but please log out or reboot for the new version to be fully functional.",
|
||||
comment: "")
|
||||
ntfPostInstall.addButton(withTitle: NSLocalizedString("OK", comment: ""))
|
||||
} else {
|
||||
if !mainInputSourceEnabled && !isMacOS12OrAbove {
|
||||
ntfPostInstall.messageText = NSLocalizedString("Warning", comment: "")
|
||||
ntfPostInstall.informativeText = NSLocalizedString("Input method may not be fully enabled. Please enable it through System Preferences > Keyboard > Input Sources.", comment: "")
|
||||
ntfPostInstall.informativeText = NSLocalizedString(
|
||||
"Input method may not be fully enabled. Please enable it through System Preferences > Keyboard > Input Sources.",
|
||||
comment: "")
|
||||
ntfPostInstall.addButton(withTitle: NSLocalizedString("Continue", comment: ""))
|
||||
} else {
|
||||
ntfPostInstall.messageText = NSLocalizedString("Installation Successful", comment: "")
|
||||
ntfPostInstall.informativeText = NSLocalizedString("vChewing is ready to use.", comment: "")
|
||||
ntfPostInstall.messageText = NSLocalizedString(
|
||||
"Installation Successful", comment: "")
|
||||
ntfPostInstall.informativeText = NSLocalizedString(
|
||||
"vChewing is ready to use.", comment: "")
|
||||
ntfPostInstall.addButton(withTitle: NSLocalizedString("OK", comment: ""))
|
||||
}
|
||||
}
|
||||
|
@ -277,9 +336,8 @@ class AppDelegate: NSWindowController, NSApplicationDelegate {
|
|||
NSApp.terminate(self)
|
||||
}
|
||||
|
||||
func windowWillClose(_ Notification: Notification) {
|
||||
func windowWillClose(_ notification: Notification) {
|
||||
NSApp.terminate(self)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -1,20 +1,27 @@
|
|||
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License).
|
||||
// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
// All possible vChewing-specific modifications are of:
|
||||
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
|
||||
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
1. The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor,
|
||||
except as required to fulfill notice requirements above.
|
||||
2. No trademark license is granted to use the trade names, trademarks, service
|
||||
marks, or product names of Contributor, except as required to fulfill notice
|
||||
requirements above.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
|
@ -35,12 +42,14 @@ struct ArchiveUtil {
|
|||
guard let resourePath = Bundle.main.resourcePath,
|
||||
let notarizedArchivesPath = notarizedArchivesPath,
|
||||
let notarizedArchive = notarizedArchive,
|
||||
let notarizedArchivesContent: [String] = try? FileManager.default.subpathsOfDirectory(atPath: notarizedArchivesPath)
|
||||
let notarizedArchivesContent: [String] = try? FileManager.default.subpathsOfDirectory(
|
||||
atPath: notarizedArchivesPath)
|
||||
else {
|
||||
return false
|
||||
}
|
||||
|
||||
let devModeAppBundlePath = (resourePath as NSString).appendingPathComponent(targetAppBundleName)
|
||||
let devModeAppBundlePath = (resourePath as NSString).appendingPathComponent(
|
||||
targetAppBundleName)
|
||||
let count = notarizedArchivesContent.count
|
||||
let notarizedArchiveExists = FileManager.default.fileExists(atPath: notarizedArchive)
|
||||
let devModeAppBundleExists = FileManager.default.fileExists(atPath: devModeAppBundlePath)
|
||||
|
@ -50,8 +59,9 @@ struct ArchiveUtil {
|
|||
let alert = NSAlert()
|
||||
alert.alertStyle = .informational
|
||||
alert.messageText = "Internal Error"
|
||||
alert.informativeText = "devMode installer, expected archive name: \(notarizedArchive), " +
|
||||
"archive exists: \(notarizedArchiveExists), devMode app bundle exists: \(devModeAppBundleExists)"
|
||||
alert.informativeText =
|
||||
"devMode installer, expected archive name: \(notarizedArchive), "
|
||||
+ "archive exists: \(notarizedArchiveExists), devMode app bundle exists: \(devModeAppBundleExists)"
|
||||
alert.addButton(withTitle: "Terminate")
|
||||
alert.runModal()
|
||||
NSApp.terminate(nil)
|
||||
|
@ -78,10 +88,12 @@ struct ArchiveUtil {
|
|||
return nil
|
||||
}
|
||||
guard let notarizedArchive = notarizedArchive,
|
||||
let resourcePath = Bundle.main.resourcePath else {
|
||||
let resourcePath = Bundle.main.resourcePath
|
||||
else {
|
||||
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 unzipTask = Process()
|
||||
unzipTask.launchPath = "/usr/bin/unzip"
|
||||
|
@ -92,7 +104,9 @@ struct ArchiveUtil {
|
|||
|
||||
assert(unzipTask.terminationStatus == 0, "Must successfully unzipped")
|
||||
let result = (tempFilePath as NSString).appendingPathComponent(targetAppBundleName)
|
||||
assert(FileManager.default.fileExists(atPath: result), "App bundle must be unzipped at \(result).")
|
||||
assert(
|
||||
FileManager.default.fileExists(atPath: result),
|
||||
"App bundle must be unzipped at \(result).")
|
||||
return result
|
||||
}
|
||||
|
||||
|
@ -100,17 +114,21 @@ struct ArchiveUtil {
|
|||
guard let resourePath = Bundle.main.resourcePath else {
|
||||
return nil
|
||||
}
|
||||
let notarizedArchivesPath = (resourePath as NSString).appendingPathComponent("NotarizedArchives")
|
||||
let notarizedArchivesPath = (resourePath as NSString).appendingPathComponent(
|
||||
"NotarizedArchives")
|
||||
return notarizedArchivesPath
|
||||
}
|
||||
|
||||
private var notarizedArchive: String? {
|
||||
guard let notarizedArchivesPath = notarizedArchivesPath,
|
||||
let bundleVersion = Bundle.main.infoDictionary?[kCFBundleVersionKey as String] as? String else {
|
||||
let bundleVersion = Bundle.main.infoDictionary?[kCFBundleVersionKey as String]
|
||||
as? String
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
let notarizedArchiveBasename = "\(appName)-r\(bundleVersion).zip"
|
||||
let notarizedArchive = (notarizedArchivesPath as NSString).appendingPathComponent(notarizedArchiveBasename)
|
||||
let notarizedArchive = (notarizedArchivesPath as NSString).appendingPathComponent(
|
||||
notarizedArchiveBasename)
|
||||
return notarizedArchive
|
||||
}
|
||||
|
||||
|
|
|
@ -1,20 +1,27 @@
|
|||
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License).
|
||||
// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
// All possible vChewing-specific modifications are of:
|
||||
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
|
||||
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
1. The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor,
|
||||
except as required to fulfill notice requirements above.
|
||||
2. No trademark license is granted to use the trade names, trademarks, service
|
||||
marks, or product names of Contributor, except as required to fulfill notice
|
||||
requirements above.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
|
|
|
@ -1,20 +1,27 @@
|
|||
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License).
|
||||
// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
// All possible vChewing-specific modifications are of:
|
||||
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
|
||||
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
1. The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor,
|
||||
except as required to fulfill notice requirements above.
|
||||
2. No trademark license is granted to use the trade names, trademarks, service
|
||||
marks, or product names of Contributor, except as required to fulfill notice
|
||||
requirements above.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
|
@ -49,37 +56,52 @@ enum VersionUpdateApiError: Error, LocalizedError {
|
|||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .connectionError(let message):
|
||||
return String(format: NSLocalizedString("There may be no internet connection or the server failed to respond.\n\nError message: %@", comment: ""), message)
|
||||
return String(
|
||||
format: NSLocalizedString(
|
||||
"There may be no internet connection or the server failed to respond.\n\nError message: %@",
|
||||
comment: ""), message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct VersionUpdateApi {
|
||||
static func check(forced: Bool, callback: @escaping (Result<VersionUpdateApiResult, Error>) -> ()) -> URLSessionTask? {
|
||||
static func check(
|
||||
forced: Bool, callback: @escaping (Result<VersionUpdateApiResult, Error>) -> Void
|
||||
) -> URLSessionTask? {
|
||||
guard let infoDict = Bundle.main.infoDictionary,
|
||||
let updateInfoURLString = infoDict[kUpdateInfoEndpointKey] as? String,
|
||||
let updateInfoURL = URL(string: updateInfoURLString) else {
|
||||
let updateInfoURL = URL(string: updateInfoURLString)
|
||||
else {
|
||||
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
|
||||
if let error = error {
|
||||
DispatchQueue.main.async {
|
||||
forced ?
|
||||
callback(.failure(VersionUpdateApiError.connectionError(message: error.localizedDescription))) :
|
||||
callback(.success(.ignored))
|
||||
forced
|
||||
? callback(
|
||||
.failure(
|
||||
VersionUpdateApiError.connectionError(
|
||||
message: error.localizedDescription)))
|
||||
: callback(.success(.ignored))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
guard let plist = try PropertyListSerialization.propertyList(from: data ?? Data(), options: [], format: nil) as? [AnyHashable: Any],
|
||||
guard
|
||||
let plist = try PropertyListSerialization.propertyList(
|
||||
from: data ?? Data(), options: [], format: nil) as? [AnyHashable: Any],
|
||||
let remoteVersion = plist[kCFBundleVersionKey] as? String,
|
||||
let infoDict = Bundle.main.infoDictionary
|
||||
else {
|
||||
DispatchQueue.main.async {
|
||||
forced ? callback(.success(.noNeedToUpdate)) : callback(.success(.ignored))
|
||||
forced
|
||||
? callback(.success(.noNeedToUpdate))
|
||||
: callback(.success(.ignored))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -88,26 +110,36 @@ struct VersionUpdateApi {
|
|||
// TODO: Use HTML to display change log, need a new key like UpdateInfoChangeLogURL for this
|
||||
|
||||
let currentVersion = infoDict[kCFBundleVersionKey as String] as? String ?? ""
|
||||
let result = currentVersion.compare(remoteVersion, options: .numeric, range: nil, locale: nil)
|
||||
let result = currentVersion.compare(
|
||||
remoteVersion, options: .numeric, range: nil, locale: nil)
|
||||
|
||||
if result != .orderedAscending {
|
||||
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
|
||||
}
|
||||
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,
|
||||
let siteInfoURL = URL(string: siteInfoURLString)
|
||||
else {
|
||||
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
|
||||
}
|
||||
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 versionDescription = ""
|
||||
let versionDescriptions = plist[kVersionDescription] as? [AnyHashable: Any]
|
||||
|
@ -118,7 +150,9 @@ struct VersionUpdateApi {
|
|||
if let first = preferredTags.first {
|
||||
locale = first
|
||||
}
|
||||
versionDescription = versionDescriptions[locale] as? String ?? versionDescriptions["en"] as? String ?? ""
|
||||
versionDescription =
|
||||
versionDescriptions[locale] as? String ?? versionDescriptions["en"]
|
||||
as? String ?? ""
|
||||
if !versionDescription.isEmpty {
|
||||
versionDescription = "\n\n" + versionDescription
|
||||
}
|
||||
|
@ -144,7 +178,9 @@ struct VersionUpdateApi {
|
|||
}
|
||||
|
||||
@objc(AppDelegate)
|
||||
class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelegate, FSEventStreamHelperDelegate {
|
||||
class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelegate,
|
||||
FSEventStreamHelperDelegate
|
||||
{
|
||||
func helper(_ helper: FSEventStreamHelper, didReceive events: [FSEventStreamHelper.Event]) {
|
||||
// 拖 100ms 再重載,畢竟有些有特殊需求的使用者可能會想使用巨型自訂語彙檔案。
|
||||
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 checkTask: URLSessionTask?
|
||||
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 = ""
|
||||
|
||||
// 補上 dealloc
|
||||
|
@ -188,7 +226,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelega
|
|||
}
|
||||
|
||||
@objc func showPreferences() {
|
||||
if (ctlPrefWindowInstance == nil) {
|
||||
if ctlPrefWindowInstance == nil {
|
||||
ctlPrefWindowInstance = ctlPrefWindow.init(windowNibName: "frmPrefWindow")
|
||||
}
|
||||
ctlPrefWindowInstance?.window?.center()
|
||||
|
@ -200,7 +238,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelega
|
|||
|
||||
// New About Window
|
||||
@objc func showAbout() {
|
||||
if (ctlAboutWindowInstance == nil) {
|
||||
if ctlAboutWindowInstance == nil {
|
||||
ctlAboutWindowInstance = ctlAboutWindow.init(windowNibName: "frmAboutWindow")
|
||||
}
|
||||
ctlAboutWindowInstance?.window?.center()
|
||||
|
@ -246,7 +284,10 @@ class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelega
|
|||
switch apiResult {
|
||||
case .shouldUpdate(let report):
|
||||
self.updateNextStepURL = report.siteUrl
|
||||
let content = String(format: NSLocalizedString("You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?%@", comment: ""),
|
||||
let content = String(
|
||||
format: NSLocalizedString(
|
||||
"You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?%@",
|
||||
comment: ""),
|
||||
report.currentShortVersion,
|
||||
report.currentVersion,
|
||||
report.remoteShortVersion,
|
||||
|
@ -254,7 +295,16 @@ class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelega
|
|||
report.versionDescription)
|
||||
IME.prtDebugIntel("vChewingDebug: \(content)")
|
||||
self.currentAlertType = "Update"
|
||||
ctlNonModalAlertWindow.shared.show(title: NSLocalizedString("New Version Available", comment: ""), content: content, confirmButtonTitle: NSLocalizedString("Visit Website", comment: ""), cancelButtonTitle: NSLocalizedString("Not Now", comment: ""), cancelAsDefault: false, delegate: self)
|
||||
ctlNonModalAlertWindow.shared.show(
|
||||
title: NSLocalizedString(
|
||||
"New Version Available", comment: ""),
|
||||
content: content,
|
||||
confirmButtonTitle: NSLocalizedString(
|
||||
"Visit Website", comment: ""),
|
||||
cancelButtonTitle: NSLocalizedString(
|
||||
"Not Now", comment: ""),
|
||||
cancelAsDefault: false,
|
||||
delegate: self)
|
||||
NSApp.setActivationPolicy(.accessory)
|
||||
case .noNeedToUpdate, .ignored:
|
||||
break
|
||||
|
@ -262,12 +312,20 @@ class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelega
|
|||
case .failure(let error):
|
||||
switch error {
|
||||
case VersionUpdateApiError.connectionError(let message):
|
||||
let title = NSLocalizedString("Update Check Failed", comment: "")
|
||||
let content = String(format: NSLocalizedString("There may be no internet connection or the server failed to respond.\n\nError message: %@", comment: ""), message)
|
||||
let title = NSLocalizedString(
|
||||
"Update Check Failed", comment: "")
|
||||
let content = String(
|
||||
format: NSLocalizedString(
|
||||
"There may be no internet connection or the server failed to respond.\n\nError message: %@",
|
||||
comment: ""), message)
|
||||
let buttonTitle = NSLocalizedString("Dismiss", comment: "")
|
||||
IME.prtDebugIntel("vChewingDebug: \(content)")
|
||||
self.currentAlertType = "Update"
|
||||
ctlNonModalAlertWindow.shared.show(title: title, content: content, confirmButtonTitle: buttonTitle, cancelButtonTitle: nil, cancelAsDefault: false, delegate: nil)
|
||||
ctlNonModalAlertWindow.shared.show(
|
||||
title: title, content: content,
|
||||
confirmButtonTitle: buttonTitle,
|
||||
cancelButtonTitle: nil,
|
||||
cancelAsDefault: false, delegate: nil)
|
||||
NSApp.setActivationPolicy(.accessory)
|
||||
default:
|
||||
break
|
||||
|
@ -278,15 +336,23 @@ class AppDelegate: NSObject, NSApplicationDelegate, ctlNonModalAlertWindowDelega
|
|||
|
||||
func selfUninstall() {
|
||||
self.currentAlertType = "Uninstall"
|
||||
let content = String(format: NSLocalizedString("This will remove vChewing Input Method from this user account, requiring your confirmation.", comment: ""))
|
||||
ctlNonModalAlertWindow.shared.show(title: NSLocalizedString("Uninstallation", comment: ""), content: content, confirmButtonTitle: NSLocalizedString("OK", comment: ""), cancelButtonTitle: NSLocalizedString("Not Now", comment: ""), cancelAsDefault: false, delegate: self)
|
||||
let content = String(
|
||||
format: NSLocalizedString(
|
||||
"This will remove vChewing Input Method from this user account, requiring your confirmation.",
|
||||
comment: ""))
|
||||
ctlNonModalAlertWindow.shared.show(
|
||||
title: NSLocalizedString("Uninstallation", comment: ""), content: content,
|
||||
confirmButtonTitle: NSLocalizedString("OK", comment: ""),
|
||||
cancelButtonTitle: NSLocalizedString("Not Now", comment: ""), cancelAsDefault: false,
|
||||
delegate: self)
|
||||
NSApp.setActivationPolicy(.accessory)
|
||||
}
|
||||
|
||||
func ctlNonModalAlertWindowDidConfirm(_ controller: ctlNonModalAlertWindow) {
|
||||
switch self.currentAlertType {
|
||||
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)
|
||||
case "Update":
|
||||
if let updateNextStepURL = self.updateNextStepURL {
|
||||
|
|
|
@ -1,19 +1,25 @@
|
|||
// Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
|
||||
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
1. The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor,
|
||||
except as required to fulfill notice requirements above.
|
||||
2. No trademark license is granted to use the trade names, trademarks, service
|
||||
marks, or product names of Contributor, except as required to fulfill notice
|
||||
requirements above.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
|
@ -56,134 +62,136 @@ import Cocoa
|
|||
if self.isDynamicBaseKeyboardLayoutEnabled() {
|
||||
// 針對不同的 Apple 動態鍵盤佈局糾正大寫英文輸入。
|
||||
switch mgrPrefs.basisKeyboardLayout {
|
||||
case "com.apple.keylayout.ZhuyinBopomofo": do {
|
||||
if (charCode == 97) {charCode = UniChar(65)}
|
||||
if (charCode == 98) {charCode = UniChar(66)}
|
||||
if (charCode == 99) {charCode = UniChar(67)}
|
||||
if (charCode == 100) {charCode = UniChar(68)}
|
||||
if (charCode == 101) {charCode = UniChar(69)}
|
||||
if (charCode == 102) {charCode = UniChar(70)}
|
||||
if (charCode == 103) {charCode = UniChar(71)}
|
||||
if (charCode == 104) {charCode = UniChar(72)}
|
||||
if (charCode == 105) {charCode = UniChar(73)}
|
||||
if (charCode == 106) {charCode = UniChar(74)}
|
||||
if (charCode == 107) {charCode = UniChar(75)}
|
||||
if (charCode == 108) {charCode = UniChar(76)}
|
||||
if (charCode == 109) {charCode = UniChar(77)}
|
||||
if (charCode == 110) {charCode = UniChar(78)}
|
||||
if (charCode == 111) {charCode = UniChar(79)}
|
||||
if (charCode == 112) {charCode = UniChar(80)}
|
||||
if (charCode == 113) {charCode = UniChar(81)}
|
||||
if (charCode == 114) {charCode = UniChar(82)}
|
||||
if (charCode == 115) {charCode = UniChar(83)}
|
||||
if (charCode == 116) {charCode = UniChar(84)}
|
||||
if (charCode == 117) {charCode = UniChar(85)}
|
||||
if (charCode == 118) {charCode = UniChar(86)}
|
||||
if (charCode == 119) {charCode = UniChar(87)}
|
||||
if (charCode == 120) {charCode = UniChar(88)}
|
||||
if (charCode == 121) {charCode = UniChar(89)}
|
||||
if (charCode == 122) {charCode = UniChar(90)}
|
||||
case "com.apple.keylayout.ZhuyinBopomofo":
|
||||
do {
|
||||
if charCode == 97 { charCode = UniChar(65) }
|
||||
if charCode == 98 { charCode = UniChar(66) }
|
||||
if charCode == 99 { charCode = UniChar(67) }
|
||||
if charCode == 100 { charCode = UniChar(68) }
|
||||
if charCode == 101 { charCode = UniChar(69) }
|
||||
if charCode == 102 { charCode = UniChar(70) }
|
||||
if charCode == 103 { charCode = UniChar(71) }
|
||||
if charCode == 104 { charCode = UniChar(72) }
|
||||
if charCode == 105 { charCode = UniChar(73) }
|
||||
if charCode == 106 { charCode = UniChar(74) }
|
||||
if charCode == 107 { charCode = UniChar(75) }
|
||||
if charCode == 108 { charCode = UniChar(76) }
|
||||
if charCode == 109 { charCode = UniChar(77) }
|
||||
if charCode == 110 { charCode = UniChar(78) }
|
||||
if charCode == 111 { charCode = UniChar(79) }
|
||||
if charCode == 112 { charCode = UniChar(80) }
|
||||
if charCode == 113 { charCode = UniChar(81) }
|
||||
if charCode == 114 { charCode = UniChar(82) }
|
||||
if charCode == 115 { charCode = UniChar(83) }
|
||||
if charCode == 116 { charCode = UniChar(84) }
|
||||
if charCode == 117 { charCode = UniChar(85) }
|
||||
if charCode == 118 { charCode = UniChar(86) }
|
||||
if charCode == 119 { charCode = UniChar(87) }
|
||||
if charCode == 120 { charCode = UniChar(88) }
|
||||
if charCode == 121 { charCode = UniChar(89) }
|
||||
if charCode == 122 { charCode = UniChar(90) }
|
||||
}
|
||||
case "com.apple.keylayout.ZhuyinEten": do {
|
||||
if (charCode == 65345) {charCode = UniChar(65)}
|
||||
if (charCode == 65346) {charCode = UniChar(66)}
|
||||
if (charCode == 65347) {charCode = UniChar(67)}
|
||||
if (charCode == 65348) {charCode = UniChar(68)}
|
||||
if (charCode == 65349) {charCode = UniChar(69)}
|
||||
if (charCode == 65350) {charCode = UniChar(70)}
|
||||
if (charCode == 65351) {charCode = UniChar(71)}
|
||||
if (charCode == 65352) {charCode = UniChar(72)}
|
||||
if (charCode == 65353) {charCode = UniChar(73)}
|
||||
if (charCode == 65354) {charCode = UniChar(74)}
|
||||
if (charCode == 65355) {charCode = UniChar(75)}
|
||||
if (charCode == 65356) {charCode = UniChar(76)}
|
||||
if (charCode == 65357) {charCode = UniChar(77)}
|
||||
if (charCode == 65358) {charCode = UniChar(78)}
|
||||
if (charCode == 65359) {charCode = UniChar(79)}
|
||||
if (charCode == 65360) {charCode = UniChar(80)}
|
||||
if (charCode == 65361) {charCode = UniChar(81)}
|
||||
if (charCode == 65362) {charCode = UniChar(82)}
|
||||
if (charCode == 65363) {charCode = UniChar(83)}
|
||||
if (charCode == 65364) {charCode = UniChar(84)}
|
||||
if (charCode == 65365) {charCode = UniChar(85)}
|
||||
if (charCode == 65366) {charCode = UniChar(86)}
|
||||
if (charCode == 65367) {charCode = UniChar(87)}
|
||||
if (charCode == 65368) {charCode = UniChar(88)}
|
||||
if (charCode == 65369) {charCode = UniChar(89)}
|
||||
if (charCode == 65370) {charCode = UniChar(90)}
|
||||
case "com.apple.keylayout.ZhuyinEten":
|
||||
do {
|
||||
if charCode == 65345 { charCode = UniChar(65) }
|
||||
if charCode == 65346 { charCode = UniChar(66) }
|
||||
if charCode == 65347 { charCode = UniChar(67) }
|
||||
if charCode == 65348 { charCode = UniChar(68) }
|
||||
if charCode == 65349 { charCode = UniChar(69) }
|
||||
if charCode == 65350 { charCode = UniChar(70) }
|
||||
if charCode == 65351 { charCode = UniChar(71) }
|
||||
if charCode == 65352 { charCode = UniChar(72) }
|
||||
if charCode == 65353 { charCode = UniChar(73) }
|
||||
if charCode == 65354 { charCode = UniChar(74) }
|
||||
if charCode == 65355 { charCode = UniChar(75) }
|
||||
if charCode == 65356 { charCode = UniChar(76) }
|
||||
if charCode == 65357 { charCode = UniChar(77) }
|
||||
if charCode == 65358 { charCode = UniChar(78) }
|
||||
if charCode == 65359 { charCode = UniChar(79) }
|
||||
if charCode == 65360 { charCode = UniChar(80) }
|
||||
if charCode == 65361 { charCode = UniChar(81) }
|
||||
if charCode == 65362 { charCode = UniChar(82) }
|
||||
if charCode == 65363 { charCode = UniChar(83) }
|
||||
if charCode == 65364 { charCode = UniChar(84) }
|
||||
if charCode == 65365 { charCode = UniChar(85) }
|
||||
if charCode == 65366 { charCode = UniChar(86) }
|
||||
if charCode == 65367 { charCode = UniChar(87) }
|
||||
if charCode == 65368 { charCode = UniChar(88) }
|
||||
if charCode == 65369 { charCode = UniChar(89) }
|
||||
if charCode == 65370 { charCode = UniChar(90) }
|
||||
}
|
||||
default: break
|
||||
}
|
||||
// 注音鍵群。
|
||||
if (charCode == 12573) {charCode = UniChar(44)}
|
||||
if (charCode == 12582) {charCode = UniChar(45)}
|
||||
if (charCode == 12577) {charCode = UniChar(46)}
|
||||
if (charCode == 12581) {charCode = UniChar(47)}
|
||||
if (charCode == 12578) {charCode = UniChar(48)}
|
||||
if (charCode == 12549) {charCode = UniChar(49)}
|
||||
if (charCode == 12553) {charCode = UniChar(50)}
|
||||
if (charCode == 711) {charCode = UniChar(51)}
|
||||
if (charCode == 715) {charCode = UniChar(52)}
|
||||
if (charCode == 12563) {charCode = UniChar(53)}
|
||||
if (charCode == 714) {charCode = UniChar(54)}
|
||||
if (charCode == 729) {charCode = UniChar(55)}
|
||||
if (charCode == 12570) {charCode = UniChar(56)}
|
||||
if (charCode == 12574) {charCode = UniChar(57)}
|
||||
if (charCode == 12580) {charCode = UniChar(59)}
|
||||
if (charCode == 12551) {charCode = UniChar(97)}
|
||||
if (charCode == 12566) {charCode = UniChar(98)}
|
||||
if (charCode == 12559) {charCode = UniChar(99)}
|
||||
if (charCode == 12558) {charCode = UniChar(100)}
|
||||
if (charCode == 12557) {charCode = UniChar(101)}
|
||||
if (charCode == 12561) {charCode = UniChar(102)}
|
||||
if (charCode == 12565) {charCode = UniChar(103)}
|
||||
if (charCode == 12568) {charCode = UniChar(104)}
|
||||
if (charCode == 12571) {charCode = UniChar(105)}
|
||||
if (charCode == 12584) {charCode = UniChar(106)}
|
||||
if (charCode == 12572) {charCode = UniChar(107)}
|
||||
if (charCode == 12576) {charCode = UniChar(108)}
|
||||
if (charCode == 12585) {charCode = UniChar(109)}
|
||||
if (charCode == 12569) {charCode = UniChar(110)}
|
||||
if (charCode == 12575) {charCode = UniChar(111)}
|
||||
if (charCode == 12579) {charCode = UniChar(112)}
|
||||
if (charCode == 12550) {charCode = UniChar(113)}
|
||||
if (charCode == 12560) {charCode = UniChar(114)}
|
||||
if (charCode == 12555) {charCode = UniChar(115)}
|
||||
if (charCode == 12564) {charCode = UniChar(116)}
|
||||
if (charCode == 12583) {charCode = UniChar(117)}
|
||||
if (charCode == 12562) {charCode = UniChar(118)}
|
||||
if (charCode == 12554) {charCode = UniChar(119)}
|
||||
if (charCode == 12556) {charCode = UniChar(120)}
|
||||
if (charCode == 12567) {charCode = UniChar(121)}
|
||||
if (charCode == 12552) {charCode = UniChar(122)}
|
||||
if charCode == 12573 { charCode = UniChar(44) }
|
||||
if charCode == 12582 { charCode = UniChar(45) }
|
||||
if charCode == 12577 { charCode = UniChar(46) }
|
||||
if charCode == 12581 { charCode = UniChar(47) }
|
||||
if charCode == 12578 { charCode = UniChar(48) }
|
||||
if charCode == 12549 { charCode = UniChar(49) }
|
||||
if charCode == 12553 { charCode = UniChar(50) }
|
||||
if charCode == 711 { charCode = UniChar(51) }
|
||||
if charCode == 715 { charCode = UniChar(52) }
|
||||
if charCode == 12563 { charCode = UniChar(53) }
|
||||
if charCode == 714 { charCode = UniChar(54) }
|
||||
if charCode == 729 { charCode = UniChar(55) }
|
||||
if charCode == 12570 { charCode = UniChar(56) }
|
||||
if charCode == 12574 { charCode = UniChar(57) }
|
||||
if charCode == 12580 { charCode = UniChar(59) }
|
||||
if charCode == 12551 { charCode = UniChar(97) }
|
||||
if charCode == 12566 { charCode = UniChar(98) }
|
||||
if charCode == 12559 { charCode = UniChar(99) }
|
||||
if charCode == 12558 { charCode = UniChar(100) }
|
||||
if charCode == 12557 { charCode = UniChar(101) }
|
||||
if charCode == 12561 { charCode = UniChar(102) }
|
||||
if charCode == 12565 { charCode = UniChar(103) }
|
||||
if charCode == 12568 { charCode = UniChar(104) }
|
||||
if charCode == 12571 { charCode = UniChar(105) }
|
||||
if charCode == 12584 { charCode = UniChar(106) }
|
||||
if charCode == 12572 { charCode = UniChar(107) }
|
||||
if charCode == 12576 { charCode = UniChar(108) }
|
||||
if charCode == 12585 { charCode = UniChar(109) }
|
||||
if charCode == 12569 { charCode = UniChar(110) }
|
||||
if charCode == 12575 { charCode = UniChar(111) }
|
||||
if charCode == 12579 { charCode = UniChar(112) }
|
||||
if charCode == 12550 { charCode = UniChar(113) }
|
||||
if charCode == 12560 { charCode = UniChar(114) }
|
||||
if charCode == 12555 { charCode = UniChar(115) }
|
||||
if charCode == 12564 { charCode = UniChar(116) }
|
||||
if charCode == 12583 { charCode = UniChar(117) }
|
||||
if charCode == 12562 { charCode = UniChar(118) }
|
||||
if charCode == 12554 { charCode = UniChar(119) }
|
||||
if charCode == 12556 { charCode = UniChar(120) }
|
||||
if charCode == 12567 { charCode = UniChar(121) }
|
||||
if charCode == 12552 { charCode = UniChar(122) }
|
||||
// 除了數字鍵區以外的標點符號。
|
||||
if (charCode == 12289) {charCode = UniChar(92)}
|
||||
if (charCode == 12300) {charCode = UniChar(91)}
|
||||
if (charCode == 12301) {charCode = UniChar(93)}
|
||||
if (charCode == 12302) {charCode = UniChar(123)}
|
||||
if (charCode == 12303) {charCode = UniChar(125)}
|
||||
if (charCode == 65292) {charCode = UniChar(60)}
|
||||
if (charCode == 12290) {charCode = UniChar(62)}
|
||||
if charCode == 12289 { charCode = UniChar(92) }
|
||||
if charCode == 12300 { charCode = UniChar(91) }
|
||||
if charCode == 12301 { charCode = UniChar(93) }
|
||||
if charCode == 12302 { charCode = UniChar(123) }
|
||||
if charCode == 12303 { charCode = UniChar(125) }
|
||||
if charCode == 65292 { charCode = UniChar(60) }
|
||||
if charCode == 12290 { charCode = UniChar(62) }
|
||||
// 摁了 SHIFT 之後的數字區的符號。
|
||||
if (charCode == 65281) {charCode = UniChar(33)}
|
||||
if (charCode == 65312) {charCode = UniChar(64)}
|
||||
if (charCode == 65283) {charCode = UniChar(35)}
|
||||
if (charCode == 65284) {charCode = UniChar(36)}
|
||||
if (charCode == 65285) {charCode = UniChar(37)}
|
||||
if (charCode == 65087) {charCode = UniChar(94)}
|
||||
if (charCode == 65286) {charCode = UniChar(38)}
|
||||
if (charCode == 65290) {charCode = UniChar(42)}
|
||||
if (charCode == 65288) {charCode = UniChar(40)}
|
||||
if (charCode == 65289) {charCode = UniChar(41)}
|
||||
if charCode == 65281 { charCode = UniChar(33) }
|
||||
if charCode == 65312 { charCode = UniChar(64) }
|
||||
if charCode == 65283 { charCode = UniChar(35) }
|
||||
if charCode == 65284 { charCode = UniChar(36) }
|
||||
if charCode == 65285 { charCode = UniChar(37) }
|
||||
if charCode == 65087 { charCode = UniChar(94) }
|
||||
if charCode == 65286 { charCode = UniChar(38) }
|
||||
if charCode == 65290 { charCode = UniChar(42) }
|
||||
if charCode == 65288 { charCode = UniChar(40) }
|
||||
if charCode == 65289 { charCode = UniChar(41) }
|
||||
// 摁了 Alt 的符號。
|
||||
if (charCode == 8212) {charCode = UniChar(45)}
|
||||
if charCode == 8212 { charCode = UniChar(45) }
|
||||
// Apple 倚天注音佈局追加符號糾正項目。
|
||||
if mgrPrefs.basisKeyboardLayout == "com.apple.keylayout.ZhuyinEten" {
|
||||
if (charCode == 65343) {charCode = UniChar(95)}
|
||||
if (charCode == 65306) {charCode = UniChar(58)}
|
||||
if (charCode == 65311) {charCode = UniChar(63)}
|
||||
if (charCode == 65291) {charCode = UniChar(43)}
|
||||
if (charCode == 65372) {charCode = UniChar(124)}
|
||||
if charCode == 65343 { charCode = UniChar(95) }
|
||||
if charCode == 65306 { charCode = UniChar(58) }
|
||||
if charCode == 65311 { charCode = UniChar(63) }
|
||||
if charCode == 65291 { charCode = UniChar(43) }
|
||||
if charCode == 65372 { charCode = UniChar(124) }
|
||||
}
|
||||
}
|
||||
return charCode
|
||||
|
@ -194,134 +202,136 @@ import Cocoa
|
|||
if self.isDynamicBaseKeyboardLayoutEnabled() {
|
||||
// 針對不同的 Apple 動態鍵盤佈局糾正大寫英文輸入。
|
||||
switch mgrPrefs.basisKeyboardLayout {
|
||||
case "com.apple.keylayout.ZhuyinBopomofo": do {
|
||||
if (strProcessed == "a") {strProcessed = "A"}
|
||||
if (strProcessed == "b") {strProcessed = "B"}
|
||||
if (strProcessed == "c") {strProcessed = "C"}
|
||||
if (strProcessed == "d") {strProcessed = "D"}
|
||||
if (strProcessed == "e") {strProcessed = "E"}
|
||||
if (strProcessed == "f") {strProcessed = "F"}
|
||||
if (strProcessed == "g") {strProcessed = "G"}
|
||||
if (strProcessed == "h") {strProcessed = "H"}
|
||||
if (strProcessed == "i") {strProcessed = "I"}
|
||||
if (strProcessed == "j") {strProcessed = "J"}
|
||||
if (strProcessed == "k") {strProcessed = "K"}
|
||||
if (strProcessed == "l") {strProcessed = "L"}
|
||||
if (strProcessed == "m") {strProcessed = "M"}
|
||||
if (strProcessed == "n") {strProcessed = "N"}
|
||||
if (strProcessed == "o") {strProcessed = "O"}
|
||||
if (strProcessed == "p") {strProcessed = "P"}
|
||||
if (strProcessed == "q") {strProcessed = "Q"}
|
||||
if (strProcessed == "r") {strProcessed = "R"}
|
||||
if (strProcessed == "s") {strProcessed = "S"}
|
||||
if (strProcessed == "t") {strProcessed = "T"}
|
||||
if (strProcessed == "u") {strProcessed = "U"}
|
||||
if (strProcessed == "v") {strProcessed = "V"}
|
||||
if (strProcessed == "w") {strProcessed = "W"}
|
||||
if (strProcessed == "x") {strProcessed = "X"}
|
||||
if (strProcessed == "y") {strProcessed = "Y"}
|
||||
if (strProcessed == "z") {strProcessed = "Z"}
|
||||
case "com.apple.keylayout.ZhuyinBopomofo":
|
||||
do {
|
||||
if strProcessed == "a" { strProcessed = "A" }
|
||||
if strProcessed == "b" { strProcessed = "B" }
|
||||
if strProcessed == "c" { strProcessed = "C" }
|
||||
if strProcessed == "d" { strProcessed = "D" }
|
||||
if strProcessed == "e" { strProcessed = "E" }
|
||||
if strProcessed == "f" { strProcessed = "F" }
|
||||
if strProcessed == "g" { strProcessed = "G" }
|
||||
if strProcessed == "h" { strProcessed = "H" }
|
||||
if strProcessed == "i" { strProcessed = "I" }
|
||||
if strProcessed == "j" { strProcessed = "J" }
|
||||
if strProcessed == "k" { strProcessed = "K" }
|
||||
if strProcessed == "l" { strProcessed = "L" }
|
||||
if strProcessed == "m" { strProcessed = "M" }
|
||||
if strProcessed == "n" { strProcessed = "N" }
|
||||
if strProcessed == "o" { strProcessed = "O" }
|
||||
if strProcessed == "p" { strProcessed = "P" }
|
||||
if strProcessed == "q" { strProcessed = "Q" }
|
||||
if strProcessed == "r" { strProcessed = "R" }
|
||||
if strProcessed == "s" { strProcessed = "S" }
|
||||
if strProcessed == "t" { strProcessed = "T" }
|
||||
if strProcessed == "u" { strProcessed = "U" }
|
||||
if strProcessed == "v" { strProcessed = "V" }
|
||||
if strProcessed == "w" { strProcessed = "W" }
|
||||
if strProcessed == "x" { strProcessed = "X" }
|
||||
if strProcessed == "y" { strProcessed = "Y" }
|
||||
if strProcessed == "z" { strProcessed = "Z" }
|
||||
}
|
||||
case "com.apple.keylayout.ZhuyinEten": do {
|
||||
if (strProcessed == "a") {strProcessed = "A"}
|
||||
if (strProcessed == "b") {strProcessed = "B"}
|
||||
if (strProcessed == "c") {strProcessed = "C"}
|
||||
if (strProcessed == "d") {strProcessed = "D"}
|
||||
if (strProcessed == "e") {strProcessed = "E"}
|
||||
if (strProcessed == "f") {strProcessed = "F"}
|
||||
if (strProcessed == "g") {strProcessed = "G"}
|
||||
if (strProcessed == "h") {strProcessed = "H"}
|
||||
if (strProcessed == "i") {strProcessed = "I"}
|
||||
if (strProcessed == "j") {strProcessed = "J"}
|
||||
if (strProcessed == "k") {strProcessed = "K"}
|
||||
if (strProcessed == "l") {strProcessed = "L"}
|
||||
if (strProcessed == "m") {strProcessed = "M"}
|
||||
if (strProcessed == "n") {strProcessed = "N"}
|
||||
if (strProcessed == "o") {strProcessed = "O"}
|
||||
if (strProcessed == "p") {strProcessed = "P"}
|
||||
if (strProcessed == "q") {strProcessed = "Q"}
|
||||
if (strProcessed == "r") {strProcessed = "R"}
|
||||
if (strProcessed == "s") {strProcessed = "S"}
|
||||
if (strProcessed == "t") {strProcessed = "T"}
|
||||
if (strProcessed == "u") {strProcessed = "U"}
|
||||
if (strProcessed == "v") {strProcessed = "V"}
|
||||
if (strProcessed == "w") {strProcessed = "W"}
|
||||
if (strProcessed == "x") {strProcessed = "X"}
|
||||
if (strProcessed == "y") {strProcessed = "Y"}
|
||||
if (strProcessed == "z") {strProcessed = "Z"}
|
||||
case "com.apple.keylayout.ZhuyinEten":
|
||||
do {
|
||||
if strProcessed == "a" { strProcessed = "A" }
|
||||
if strProcessed == "b" { strProcessed = "B" }
|
||||
if strProcessed == "c" { strProcessed = "C" }
|
||||
if strProcessed == "d" { strProcessed = "D" }
|
||||
if strProcessed == "e" { strProcessed = "E" }
|
||||
if strProcessed == "f" { strProcessed = "F" }
|
||||
if strProcessed == "g" { strProcessed = "G" }
|
||||
if strProcessed == "h" { strProcessed = "H" }
|
||||
if strProcessed == "i" { strProcessed = "I" }
|
||||
if strProcessed == "j" { strProcessed = "J" }
|
||||
if strProcessed == "k" { strProcessed = "K" }
|
||||
if strProcessed == "l" { strProcessed = "L" }
|
||||
if strProcessed == "m" { strProcessed = "M" }
|
||||
if strProcessed == "n" { strProcessed = "N" }
|
||||
if strProcessed == "o" { strProcessed = "O" }
|
||||
if strProcessed == "p" { strProcessed = "P" }
|
||||
if strProcessed == "q" { strProcessed = "Q" }
|
||||
if strProcessed == "r" { strProcessed = "R" }
|
||||
if strProcessed == "s" { strProcessed = "S" }
|
||||
if strProcessed == "t" { strProcessed = "T" }
|
||||
if strProcessed == "u" { strProcessed = "U" }
|
||||
if strProcessed == "v" { strProcessed = "V" }
|
||||
if strProcessed == "w" { strProcessed = "W" }
|
||||
if strProcessed == "x" { strProcessed = "X" }
|
||||
if strProcessed == "y" { strProcessed = "Y" }
|
||||
if strProcessed == "z" { strProcessed = "Z" }
|
||||
}
|
||||
default: break
|
||||
}
|
||||
// 注音鍵群。
|
||||
if (strProcessed == "ㄝ") {strProcessed = ","}
|
||||
if (strProcessed == "ㄦ") {strProcessed = "-"}
|
||||
if (strProcessed == "ㄡ") {strProcessed = "."}
|
||||
if (strProcessed == "ㄥ") {strProcessed = "/"}
|
||||
if (strProcessed == "ㄢ") {strProcessed = "0"}
|
||||
if (strProcessed == "ㄅ") {strProcessed = "1"}
|
||||
if (strProcessed == "ㄉ") {strProcessed = "2"}
|
||||
if (strProcessed == "ˇ") {strProcessed = "3"}
|
||||
if (strProcessed == "ˋ") {strProcessed = "4"}
|
||||
if (strProcessed == "ㄓ") {strProcessed = "5"}
|
||||
if (strProcessed == "ˊ") {strProcessed = "6"}
|
||||
if (strProcessed == "˙") {strProcessed = "7"}
|
||||
if (strProcessed == "ㄚ") {strProcessed = "8"}
|
||||
if (strProcessed == "ㄞ") {strProcessed = "9"}
|
||||
if (strProcessed == "ㄤ") {strProcessed = ";"}
|
||||
if (strProcessed == "ㄇ") {strProcessed = "a"}
|
||||
if (strProcessed == "ㄖ") {strProcessed = "b"}
|
||||
if (strProcessed == "ㄏ") {strProcessed = "c"}
|
||||
if (strProcessed == "ㄎ") {strProcessed = "d"}
|
||||
if (strProcessed == "ㄍ") {strProcessed = "e"}
|
||||
if (strProcessed == "ㄑ") {strProcessed = "f"}
|
||||
if (strProcessed == "ㄕ") {strProcessed = "g"}
|
||||
if (strProcessed == "ㄘ") {strProcessed = "h"}
|
||||
if (strProcessed == "ㄛ") {strProcessed = "i"}
|
||||
if (strProcessed == "ㄨ") {strProcessed = "j"}
|
||||
if (strProcessed == "ㄜ") {strProcessed = "k"}
|
||||
if (strProcessed == "ㄠ") {strProcessed = "l"}
|
||||
if (strProcessed == "ㄩ") {strProcessed = "m"}
|
||||
if (strProcessed == "ㄙ") {strProcessed = "n"}
|
||||
if (strProcessed == "ㄟ") {strProcessed = "o"}
|
||||
if (strProcessed == "ㄣ") {strProcessed = "p"}
|
||||
if (strProcessed == "ㄆ") {strProcessed = "q"}
|
||||
if (strProcessed == "ㄐ") {strProcessed = "r"}
|
||||
if (strProcessed == "ㄋ") {strProcessed = "s"}
|
||||
if (strProcessed == "ㄔ") {strProcessed = "t"}
|
||||
if (strProcessed == "ㄧ") {strProcessed = "u"}
|
||||
if (strProcessed == "ㄒ") {strProcessed = "v"}
|
||||
if (strProcessed == "ㄊ") {strProcessed = "w"}
|
||||
if (strProcessed == "ㄌ") {strProcessed = "x"}
|
||||
if (strProcessed == "ㄗ") {strProcessed = "y"}
|
||||
if (strProcessed == "ㄈ") {strProcessed = "z"}
|
||||
if strProcessed == "ㄝ" { strProcessed = "," }
|
||||
if strProcessed == "ㄦ" { strProcessed = "-" }
|
||||
if strProcessed == "ㄡ" { strProcessed = "." }
|
||||
if strProcessed == "ㄥ" { strProcessed = "/" }
|
||||
if strProcessed == "ㄢ" { strProcessed = "0" }
|
||||
if strProcessed == "ㄅ" { strProcessed = "1" }
|
||||
if strProcessed == "ㄉ" { strProcessed = "2" }
|
||||
if strProcessed == "ˇ" { strProcessed = "3" }
|
||||
if strProcessed == "ˋ" { strProcessed = "4" }
|
||||
if strProcessed == "ㄓ" { strProcessed = "5" }
|
||||
if strProcessed == "ˊ" { strProcessed = "6" }
|
||||
if strProcessed == "˙" { strProcessed = "7" }
|
||||
if strProcessed == "ㄚ" { strProcessed = "8" }
|
||||
if strProcessed == "ㄞ" { strProcessed = "9" }
|
||||
if strProcessed == "ㄤ" { strProcessed = ";" }
|
||||
if strProcessed == "ㄇ" { strProcessed = "a" }
|
||||
if strProcessed == "ㄖ" { strProcessed = "b" }
|
||||
if strProcessed == "ㄏ" { strProcessed = "c" }
|
||||
if strProcessed == "ㄎ" { strProcessed = "d" }
|
||||
if strProcessed == "ㄍ" { strProcessed = "e" }
|
||||
if strProcessed == "ㄑ" { strProcessed = "f" }
|
||||
if strProcessed == "ㄕ" { strProcessed = "g" }
|
||||
if strProcessed == "ㄘ" { strProcessed = "h" }
|
||||
if strProcessed == "ㄛ" { strProcessed = "i" }
|
||||
if strProcessed == "ㄨ" { strProcessed = "j" }
|
||||
if strProcessed == "ㄜ" { strProcessed = "k" }
|
||||
if strProcessed == "ㄠ" { strProcessed = "l" }
|
||||
if strProcessed == "ㄩ" { strProcessed = "m" }
|
||||
if strProcessed == "ㄙ" { strProcessed = "n" }
|
||||
if strProcessed == "ㄟ" { strProcessed = "o" }
|
||||
if strProcessed == "ㄣ" { strProcessed = "p" }
|
||||
if strProcessed == "ㄆ" { strProcessed = "q" }
|
||||
if strProcessed == "ㄐ" { strProcessed = "r" }
|
||||
if strProcessed == "ㄋ" { strProcessed = "s" }
|
||||
if strProcessed == "ㄔ" { strProcessed = "t" }
|
||||
if strProcessed == "ㄧ" { strProcessed = "u" }
|
||||
if strProcessed == "ㄒ" { strProcessed = "v" }
|
||||
if strProcessed == "ㄊ" { strProcessed = "w" }
|
||||
if strProcessed == "ㄌ" { strProcessed = "x" }
|
||||
if strProcessed == "ㄗ" { strProcessed = "y" }
|
||||
if strProcessed == "ㄈ" { strProcessed = "z" }
|
||||
// 除了數字鍵區以外的標點符號。
|
||||
if (strProcessed == "、") {strProcessed = "\\"}
|
||||
if (strProcessed == "「") {strProcessed = "["}
|
||||
if (strProcessed == "」") {strProcessed = "]"}
|
||||
if (strProcessed == "『") {strProcessed = "{"}
|
||||
if (strProcessed == "』") {strProcessed = "}"}
|
||||
if (strProcessed == ",") {strProcessed = "<"}
|
||||
if (strProcessed == "。") {strProcessed = ">"}
|
||||
if strProcessed == "、" { strProcessed = "\\" }
|
||||
if strProcessed == "「" { strProcessed = "[" }
|
||||
if strProcessed == "」" { strProcessed = "]" }
|
||||
if strProcessed == "『" { strProcessed = "{" }
|
||||
if strProcessed == "』" { strProcessed = "}" }
|
||||
if strProcessed == "," { strProcessed = "<" }
|
||||
if strProcessed == "。" { strProcessed = ">" }
|
||||
// 摁了 SHIFT 之後的數字區的符號。
|
||||
if (strProcessed == "!") {strProcessed = "!"}
|
||||
if (strProcessed == "@") {strProcessed = "@"}
|
||||
if (strProcessed == "#") {strProcessed = "#"}
|
||||
if (strProcessed == "$") {strProcessed = "$"}
|
||||
if (strProcessed == "%") {strProcessed = "%"}
|
||||
if (strProcessed == "︿") {strProcessed = "^"}
|
||||
if (strProcessed == "&") {strProcessed = "&"}
|
||||
if (strProcessed == "*") {strProcessed = "*"}
|
||||
if (strProcessed == "(") {strProcessed = "("}
|
||||
if (strProcessed == ")") {strProcessed = ")"}
|
||||
if strProcessed == "!" { strProcessed = "!" }
|
||||
if strProcessed == "@" { strProcessed = "@" }
|
||||
if strProcessed == "#" { strProcessed = "#" }
|
||||
if strProcessed == "$" { strProcessed = "$" }
|
||||
if strProcessed == "%" { strProcessed = "%" }
|
||||
if strProcessed == "︿" { strProcessed = "^" }
|
||||
if strProcessed == "&" { strProcessed = "&" }
|
||||
if strProcessed == "*" { strProcessed = "*" }
|
||||
if strProcessed == "(" { strProcessed = "(" }
|
||||
if strProcessed == ")" { strProcessed = ")" }
|
||||
// 摁了 Alt 的符號。
|
||||
if (strProcessed == "—") {strProcessed = "-"}
|
||||
if strProcessed == "—" { strProcessed = "-" }
|
||||
// Apple 倚天注音佈局追加符號糾正項目。
|
||||
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
|
||||
|
|
|
@ -1,20 +1,27 @@
|
|||
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License).
|
||||
// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
// All possible vChewing-specific modifications are of:
|
||||
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
|
||||
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
1. The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor,
|
||||
except as required to fulfill notice requirements above.
|
||||
2. No trademark license is granted to use the trade names, trademarks, service
|
||||
marks, or product names of Contributor, except as required to fulfill notice
|
||||
requirements above.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
|
@ -53,7 +60,7 @@ import Cocoa
|
|||
class InputState: NSObject {
|
||||
|
||||
/// Represents that the input controller is deactivated.
|
||||
@objc (InputStateDeactivated)
|
||||
@objc(InputStateDeactivated)
|
||||
class Deactivated: InputState {
|
||||
override var description: String {
|
||||
"<InputState.Deactivated>"
|
||||
|
@ -63,7 +70,7 @@ class InputState: NSObject {
|
|||
// MARK: -
|
||||
|
||||
/// Represents that the composing buffer is empty.
|
||||
@objc (InputStateEmpty)
|
||||
@objc(InputStateEmpty)
|
||||
class Empty: InputState {
|
||||
@objc var composingBuffer: String {
|
||||
""
|
||||
|
@ -77,7 +84,7 @@ class InputState: NSObject {
|
|||
// MARK: -
|
||||
|
||||
/// Represents that the composing buffer is empty.
|
||||
@objc (InputStateEmptyIgnoringPreviousState)
|
||||
@objc(InputStateEmptyIgnoringPreviousState)
|
||||
class EmptyIgnoringPreviousState: InputState {
|
||||
@objc var composingBuffer: String {
|
||||
""
|
||||
|
@ -90,7 +97,7 @@ class InputState: NSObject {
|
|||
// MARK: -
|
||||
|
||||
/// Represents that the input controller is committing text into client app.
|
||||
@objc (InputStateCommitting)
|
||||
@objc(InputStateCommitting)
|
||||
class Committing: InputState {
|
||||
@objc private(set) var poppedText: String = ""
|
||||
|
||||
|
@ -107,7 +114,7 @@ class InputState: NSObject {
|
|||
// MARK: -
|
||||
|
||||
/// Represents that the composing buffer is not empty.
|
||||
@objc (InputStateNotEmpty)
|
||||
@objc(InputStateNotEmpty)
|
||||
class NotEmpty: InputState {
|
||||
@objc private(set) var composingBuffer: String
|
||||
@objc private(set) var cursorIndex: UInt
|
||||
|
@ -125,7 +132,7 @@ class InputState: NSObject {
|
|||
// MARK: -
|
||||
|
||||
/// Represents that the user is inputting text.
|
||||
@objc (InputStateInputting)
|
||||
@objc(InputStateInputting)
|
||||
class Inputting: NotEmpty {
|
||||
@objc var poppedText: String = ""
|
||||
@objc var tooltip: String = ""
|
||||
|
@ -135,9 +142,11 @@ class InputState: NSObject {
|
|||
}
|
||||
|
||||
@objc var attributedString: NSAttributedString {
|
||||
let attributedSting = NSAttributedString(string: composingBuffer, attributes: [
|
||||
let attributedSting = NSAttributedString(
|
||||
string: composingBuffer,
|
||||
attributes: [
|
||||
.underlineStyle: NSUnderlineStyle.single.rawValue,
|
||||
.markedClauseSegment: 0
|
||||
.markedClauseSegment: 0,
|
||||
])
|
||||
return attributedSting
|
||||
}
|
||||
|
@ -153,7 +162,7 @@ class InputState: NSObject {
|
|||
private let kMaxMarkRangeLength = mgrPrefs.maxCandidateLength
|
||||
|
||||
/// Represents that the user is marking a range in the composing buffer.
|
||||
@objc (InputStateMarking)
|
||||
@objc(InputStateMarking)
|
||||
class Marking: NotEmpty {
|
||||
|
||||
@objc private(set) var markerIndex: UInt
|
||||
|
@ -162,15 +171,19 @@ class InputState: NSObject {
|
|||
@objc var tooltip: String {
|
||||
|
||||
if composingBuffer.count != readings.count {
|
||||
TooltipController.backgroundColor = NSColor(red: 0.55, green: 0.00, blue: 0.00, alpha: 1.00)
|
||||
TooltipController.backgroundColor = NSColor(
|
||||
red: 0.55, green: 0.00, blue: 0.00, alpha: 1.00)
|
||||
TooltipController.textColor = NSColor.white
|
||||
return NSLocalizedString("⚠︎ Unhandlable: Chars and Readings in buffer doesn't match.", comment: "")
|
||||
return NSLocalizedString(
|
||||
"⚠︎ Unhandlable: Chars and Readings in buffer doesn't match.", comment: "")
|
||||
}
|
||||
|
||||
if mgrPrefs.phraseReplacementEnabled {
|
||||
TooltipController.backgroundColor = NSColor.purple
|
||||
TooltipController.textColor = NSColor.white
|
||||
return NSLocalizedString("⚠︎ Phrase replacement mode enabled, interfering user phrase entry.", comment: "")
|
||||
return NSLocalizedString(
|
||||
"⚠︎ Phrase replacement mode enabled, interfering user phrase entry.", comment: ""
|
||||
)
|
||||
}
|
||||
if markedRange.length == 0 {
|
||||
return ""
|
||||
|
@ -178,35 +191,57 @@ class InputState: NSObject {
|
|||
|
||||
let text = (composingBuffer as NSString).substring(with: markedRange)
|
||||
if markedRange.length < kMinMarkRangeLength {
|
||||
TooltipController.backgroundColor = NSColor(red: 0.18, green: 0.18, blue: 0.18, alpha: 1.00)
|
||||
TooltipController.textColor = NSColor(red: 0.86, green: 0.86, blue: 0.86, alpha: 1.00)
|
||||
return String(format: NSLocalizedString("\"%@\" length must ≥ 2 for a user phrase.", comment: ""), text)
|
||||
} else if (markedRange.length > kMaxMarkRangeLength) {
|
||||
TooltipController.backgroundColor = NSColor(red: 0.26, green: 0.16, blue: 0.00, alpha: 1.00)
|
||||
TooltipController.textColor = NSColor(red: 1.00, green: 0.60, blue: 0.00, alpha: 1.00)
|
||||
return String(format: NSLocalizedString("\"%@\" length should ≤ %d for a user phrase.", comment: ""), text, kMaxMarkRangeLength)
|
||||
TooltipController.backgroundColor = NSColor(
|
||||
red: 0.18, green: 0.18, blue: 0.18, alpha: 1.00)
|
||||
TooltipController.textColor = NSColor(
|
||||
red: 0.86, green: 0.86, blue: 0.86, alpha: 1.00)
|
||||
return String(
|
||||
format: NSLocalizedString(
|
||||
"\"%@\" length must ≥ 2 for a user phrase.", comment: ""), text)
|
||||
} else if markedRange.length > kMaxMarkRangeLength {
|
||||
TooltipController.backgroundColor = NSColor(
|
||||
red: 0.26, green: 0.16, blue: 0.00, alpha: 1.00)
|
||||
TooltipController.textColor = NSColor(
|
||||
red: 1.00, green: 0.60, blue: 0.00, alpha: 1.00)
|
||||
return String(
|
||||
format: NSLocalizedString(
|
||||
"\"%@\" length should ≤ %d for a user phrase.", comment: ""),
|
||||
text, kMaxMarkRangeLength)
|
||||
}
|
||||
|
||||
let (exactBegin, _) = (composingBuffer as NSString).characterIndex(from: markedRange.location)
|
||||
let (exactEnd, _) = (composingBuffer as NSString).characterIndex(from: markedRange.location + markedRange.length)
|
||||
let (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 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 {
|
||||
deleteTargetExists = exist
|
||||
TooltipController.backgroundColor = NSColor(red: 0.00, green: 0.18, blue: 0.13, alpha: 1.00)
|
||||
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.00, green: 0.18, blue: 0.13, alpha: 1.00)
|
||||
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
|
||||
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 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
|
||||
let begin = min(cursorIndex, markerIndex)
|
||||
let end = max(cursorIndex, markerIndex)
|
||||
|
@ -219,18 +254,23 @@ class InputState: NSObject {
|
|||
let attributedSting = NSMutableAttributedString(string: composingBuffer)
|
||||
let end = markedRange.location + markedRange.length
|
||||
|
||||
attributedSting.setAttributes([
|
||||
attributedSting.setAttributes(
|
||||
[
|
||||
.underlineStyle: NSUnderlineStyle.single.rawValue,
|
||||
.markedClauseSegment: 0
|
||||
.markedClauseSegment: 0,
|
||||
], range: NSRange(location: 0, length: markedRange.location))
|
||||
attributedSting.setAttributes([
|
||||
attributedSting.setAttributes(
|
||||
[
|
||||
.underlineStyle: NSUnderlineStyle.thick.rawValue,
|
||||
.markedClauseSegment: 1
|
||||
.markedClauseSegment: 1,
|
||||
], range: markedRange)
|
||||
attributedSting.setAttributes([
|
||||
attributedSting.setAttributes(
|
||||
[
|
||||
.underlineStyle: NSUnderlineStyle.single.rawValue,
|
||||
.markedClauseSegment: 2
|
||||
], range: NSRange(location: end,
|
||||
.markedClauseSegment: 2,
|
||||
],
|
||||
range: NSRange(
|
||||
location: end,
|
||||
length: (composingBuffer as NSString).length - end))
|
||||
return attributedSting
|
||||
}
|
||||
|
@ -262,31 +302,42 @@ class InputState: NSObject {
|
|||
if ctlInputMethod.areWeDeleting && !deleteTargetExists {
|
||||
return false
|
||||
}
|
||||
return markedRange.length >= kMinMarkRangeLength && markedRange.length <= kMaxMarkRangeLength
|
||||
return markedRange.length >= kMinMarkRangeLength
|
||||
&& markedRange.length <= kMaxMarkRangeLength
|
||||
}
|
||||
|
||||
@objc var chkIfUserPhraseExists: Bool {
|
||||
let text = (composingBuffer as NSString).substring(with: markedRange)
|
||||
let (exactBegin, _) = (composingBuffer as NSString).characterIndex(from: markedRange.location)
|
||||
let (exactEnd, _) = (composingBuffer as NSString).characterIndex(from: markedRange.location + markedRange.length)
|
||||
let (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 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 {
|
||||
let text = (composingBuffer as NSString).substring(with: markedRange)
|
||||
let (exactBegin, _) = (composingBuffer as NSString).characterIndex(from: markedRange.location)
|
||||
let (exactEnd, _) = (composingBuffer as NSString).characterIndex(from: markedRange.location + markedRange.length)
|
||||
let (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 joined = selectedReadings.joined(separator: "-")
|
||||
return "\(text) \(joined)"
|
||||
}
|
||||
|
||||
@objc var userPhraseConverted: String {
|
||||
let text = OpenCCBridge.crossConvert((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 text =
|
||||
OpenCCBridge.crossConvert(
|
||||
(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 joined = selectedReadings.joined(separator: "-")
|
||||
let convertedMark = "#𝙊𝙥𝙚𝙣𝘾𝘾"
|
||||
|
@ -297,21 +348,25 @@ class InputState: NSObject {
|
|||
// MARK: -
|
||||
|
||||
/// Represents that the user is choosing in a candidates list.
|
||||
@objc (InputStateChoosingCandidate)
|
||||
@objc(InputStateChoosingCandidate)
|
||||
class ChoosingCandidate: NotEmpty {
|
||||
@objc private(set) var candidates: [String]
|
||||
@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.useVerticalMode = useVerticalMode
|
||||
super.init(composingBuffer: composingBuffer, cursorIndex: cursorIndex)
|
||||
}
|
||||
|
||||
@objc var attributedString: NSAttributedString {
|
||||
let attributedSting = NSAttributedString(string: composingBuffer, attributes: [
|
||||
let attributedSting = NSAttributedString(
|
||||
string: composingBuffer,
|
||||
attributes: [
|
||||
.underlineStyle: NSUnderlineStyle.single.rawValue,
|
||||
.markedClauseSegment: 0
|
||||
.markedClauseSegment: 0,
|
||||
])
|
||||
return attributedSting
|
||||
}
|
||||
|
@ -325,7 +380,7 @@ class InputState: NSObject {
|
|||
|
||||
/// Represents that the user is choosing in a candidates list
|
||||
/// in the associated phrases mode.
|
||||
@objc (InputStateAssociatedPhrases)
|
||||
@objc(InputStateAssociatedPhrases)
|
||||
class AssociatedPhrases: InputState {
|
||||
@objc private(set) var candidates: [String] = []
|
||||
@objc private(set) var useVerticalMode: Bool = false
|
||||
|
@ -340,14 +395,16 @@ class InputState: NSObject {
|
|||
}
|
||||
}
|
||||
|
||||
@objc (InputStateSymbolTable)
|
||||
@objc(InputStateSymbolTable)
|
||||
class SymbolTable: ChoosingCandidate {
|
||||
@objc var node: SymbolNode
|
||||
|
||||
@objc init(node: SymbolNode, useVerticalMode: Bool) {
|
||||
self.node = node
|
||||
let candidates = node.children?.map { $0.title } ?? [String]()
|
||||
super.init(composingBuffer: "", cursorIndex: 0, candidates: candidates, useVerticalMode: useVerticalMode)
|
||||
super.init(
|
||||
composingBuffer: "", cursorIndex: 0, candidates: candidates,
|
||||
useVerticalMode: useVerticalMode)
|
||||
}
|
||||
|
||||
override var description: String {
|
||||
|
@ -373,38 +430,57 @@ class InputState: NSObject {
|
|||
super.init()
|
||||
}
|
||||
|
||||
@objc static let catCommonSymbols = String(format: NSLocalizedString("catCommonSymbols", comment: ""))
|
||||
@objc static let catHoriBrackets = String(format: NSLocalizedString("catHoriBrackets", comment: ""))
|
||||
@objc static let catVertBrackets = String(format: NSLocalizedString("catVertBrackets", comment: ""))
|
||||
@objc static let catGreekLetters = String(format: NSLocalizedString("catGreekLetters", comment: ""))
|
||||
@objc static let catMathSymbols = String(format: NSLocalizedString("catMathSymbols", comment: ""))
|
||||
@objc static let catCurrencyUnits = String(format: NSLocalizedString("catCurrencyUnits", comment: ""))
|
||||
@objc static let catSpecialSymbols = String(format: NSLocalizedString("catSpecialSymbols", comment: ""))
|
||||
@objc static let catUnicodeSymbols = String(format: NSLocalizedString("catUnicodeSymbols", comment: ""))
|
||||
@objc static let catCircledKanjis = String(format: NSLocalizedString("catCircledKanjis", comment: ""))
|
||||
@objc static let catCircledKataKana = String(format: NSLocalizedString("catCircledKataKana", comment: ""))
|
||||
@objc static let catBracketKanjis = String(format: NSLocalizedString("catBracketKanjis", comment: ""))
|
||||
@objc static let catSingleTableLines = String(format: NSLocalizedString("catSingleTableLines", comment: ""))
|
||||
@objc static let catDoubleTableLines = String(format: NSLocalizedString("catDoubleTableLines", comment: ""))
|
||||
@objc static let catFillingBlocks = String(format: NSLocalizedString("catFillingBlocks", comment: ""))
|
||||
@objc static let catLineSegments = String(format: NSLocalizedString("catLineSegments", comment: ""))
|
||||
@objc static let catCommonSymbols = String(
|
||||
format: NSLocalizedString("catCommonSymbols", comment: ""))
|
||||
@objc static let catHoriBrackets = String(
|
||||
format: NSLocalizedString("catHoriBrackets", comment: ""))
|
||||
@objc static let catVertBrackets = String(
|
||||
format: NSLocalizedString("catVertBrackets", comment: ""))
|
||||
@objc static let catGreekLetters = String(
|
||||
format: NSLocalizedString("catGreekLetters", comment: ""))
|
||||
@objc static let catMathSymbols = String(
|
||||
format: NSLocalizedString("catMathSymbols", comment: ""))
|
||||
@objc static let catCurrencyUnits = String(
|
||||
format: NSLocalizedString("catCurrencyUnits", comment: ""))
|
||||
@objc static let catSpecialSymbols = String(
|
||||
format: NSLocalizedString("catSpecialSymbols", comment: ""))
|
||||
@objc static let catUnicodeSymbols = String(
|
||||
format: NSLocalizedString("catUnicodeSymbols", comment: ""))
|
||||
@objc static let catCircledKanjis = String(
|
||||
format: NSLocalizedString("catCircledKanjis", comment: ""))
|
||||
@objc static let catCircledKataKana = String(
|
||||
format: NSLocalizedString("catCircledKataKana", comment: ""))
|
||||
@objc static let catBracketKanjis = String(
|
||||
format: NSLocalizedString("catBracketKanjis", comment: ""))
|
||||
@objc static let catSingleTableLines = String(
|
||||
format: NSLocalizedString("catSingleTableLines", comment: ""))
|
||||
@objc static let catDoubleTableLines = String(
|
||||
format: NSLocalizedString("catDoubleTableLines", comment: ""))
|
||||
@objc static let catFillingBlocks = String(
|
||||
format: NSLocalizedString("catFillingBlocks", comment: ""))
|
||||
@objc static let catLineSegments = String(
|
||||
format: NSLocalizedString("catLineSegments", comment: ""))
|
||||
|
||||
@objc static let root: SymbolNode = SymbolNode("/", [
|
||||
@objc static let root: SymbolNode = SymbolNode(
|
||||
"/",
|
||||
[
|
||||
SymbolNode("`"),
|
||||
SymbolNode(catCommonSymbols, symbols:",、。.?!;:‧‥﹐﹒˙·‘’“”〝〞‵′〃~$%@&#*"),
|
||||
SymbolNode(catHoriBrackets, symbols:"()「」〔〕{}〈〉『』《》【】﹙﹚﹝﹞﹛﹜"),
|
||||
SymbolNode(catVertBrackets, symbols:"︵︶﹁﹂︹︺︷︸︿﹀﹃﹄︽︾︻︼"),
|
||||
SymbolNode(catGreekLetters, symbols:"αβγδεζηθικλμνξοπρστυφχψωΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ"),
|
||||
SymbolNode(catMathSymbols, symbols:"+-×÷=≠≒∞±√<>﹤﹥≦≧∩∪ˇ⊥∠∟⊿㏒㏑∫∮∵∴╳﹢"),
|
||||
SymbolNode(catCurrencyUnits, symbols:"$€¥¢£₽₨₩฿₺₮₱₭₴₦৲৳૱௹﷼₹₲₪₡₫៛₵₢₸₤₳₥₠₣₰₧₯₶₷"),
|
||||
SymbolNode(catSpecialSymbols, symbols:"↑↓←→↖↗↙↘↺⇧⇩⇦⇨⇄⇆⇅⇵↻◎○●⊕⊙※△▲☆★◇◆□■▽▼§¥〒¢£♀♂↯"),
|
||||
SymbolNode(catUnicodeSymbols, symbols:"♨☀☁☂☃♠♥♣♦♩♪♫♬☺☻"),
|
||||
SymbolNode(catCircledKanjis, symbols:"㊟㊞㊚㊛㊊㊋㊌㊍㊎㊏㊐㊑㊒㊓㊔㊕㊖㊗︎㊘㊙︎㊜㊝㊠㊡㊢㊣㊤㊥㊦㊧㊨㊩㊪㊫㊬㊭㊮㊯㊰🈚︎🈯︎"),
|
||||
SymbolNode(catCircledKataKana, symbols:"㋐㋑㋒㋓㋔㋕㋖㋗㋘㋙㋚㋛㋜㋝㋞㋟㋠㋡㋢㋣㋤㋥㋦㋧㋨㋩㋪㋫㋬㋭㋮㋯㋰㋱㋲㋳㋴㋵㋶㋷㋸㋹㋺㋻㋼㋾"),
|
||||
SymbolNode(catBracketKanjis, symbols:"㈪㈫㈬㈭㈮㈯㈰㈱㈲㈳㈴㈵㈶㈷㈸㈹㈺㈻㈼㈽㈾㈿㉀㉁㉂㉃"),
|
||||
SymbolNode(catSingleTableLines, symbols:"├─┼┴┬┤┌┐╞═╪╡│▕└┘╭╮╰╯"),
|
||||
SymbolNode(catDoubleTableLines, symbols:"╔╦╗╠═╬╣╓╥╖╒╤╕║╚╩╝╟╫╢╙╨╜╞╪╡╘╧╛"),
|
||||
SymbolNode(catFillingBlocks, symbols:"_ˍ▁▂▃▄▅▆▇█▏▎▍▌▋▊▉◢◣◥◤"),
|
||||
SymbolNode(catLineSegments, symbols:"﹣﹦≡|∣∥–︱—︳╴¯ ̄﹉﹊﹍﹎﹋﹌﹏︴∕﹨╱╲/\"),
|
||||
SymbolNode(catCommonSymbols, symbols: ",、。.?!;:‧‥﹐﹒˙·‘’“”〝〞‵′〃~$%@&#*"),
|
||||
SymbolNode(catHoriBrackets, symbols: "()「」〔〕{}〈〉『』《》【】﹙﹚﹝﹞﹛﹜"),
|
||||
SymbolNode(catVertBrackets, symbols: "︵︶﹁﹂︹︺︷︸︿﹀﹃﹄︽︾︻︼"),
|
||||
SymbolNode(
|
||||
catGreekLetters, symbols: "αβγδεζηθικλμνξοπρστυφχψωΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ"),
|
||||
SymbolNode(catMathSymbols, symbols: "+-×÷=≠≒∞±√<>﹤﹥≦≧∩∪ˇ⊥∠∟⊿㏒㏑∫∮∵∴╳﹢"),
|
||||
SymbolNode(catCurrencyUnits, symbols: "$€¥¢£₽₨₩฿₺₮₱₭₴₦৲৳૱௹﷼₹₲₪₡₫៛₵₢₸₤₳₥₠₣₰₧₯₶₷"),
|
||||
SymbolNode(catSpecialSymbols, symbols: "↑↓←→↖↗↙↘↺⇧⇩⇦⇨⇄⇆⇅⇵↻◎○●⊕⊙※△▲☆★◇◆□■▽▼§¥〒¢£♀♂↯"),
|
||||
SymbolNode(catUnicodeSymbols, symbols: "♨☀☁☂☃♠♥♣♦♩♪♫♬☺☻"),
|
||||
SymbolNode(catCircledKanjis, symbols: "㊟㊞㊚㊛㊊㊋㊌㊍㊎㊏㊐㊑㊒㊓㊔㊕㊖㊗︎㊘㊙︎㊜㊝㊠㊡㊢㊣㊤㊥㊦㊧㊨㊩㊪㊫㊬㊭㊮㊯㊰🈚︎🈯︎"),
|
||||
SymbolNode(
|
||||
catCircledKataKana, symbols: "㋐㋑㋒㋓㋔㋕㋖㋗㋘㋙㋚㋛㋜㋝㋞㋟㋠㋡㋢㋣㋤㋥㋦㋧㋨㋩㋪㋫㋬㋭㋮㋯㋰㋱㋲㋳㋴㋵㋶㋷㋸㋹㋺㋻㋼㋾"),
|
||||
SymbolNode(catBracketKanjis, symbols: "㈪㈫㈬㈭㈮㈯㈰㈱㈲㈳㈴㈵㈶㈷㈸㈹㈺㈻㈼㈽㈾㈿㉀㉁㉂㉃"),
|
||||
SymbolNode(catSingleTableLines, symbols: "├─┼┴┬┤┌┐╞═╪╡│▕└┘╭╮╰╯"),
|
||||
SymbolNode(catDoubleTableLines, symbols: "╔╦╗╠═╬╣╓╥╖╒╤╕║╚╩╝╟╫╢╙╨╜╞╪╡╘╧╛"),
|
||||
SymbolNode(catFillingBlocks, symbols: "_ˍ▁▂▃▄▅▆▇█▏▎▍▌▋▊▉◢◣◥◤"),
|
||||
SymbolNode(catLineSegments, symbols: "﹣﹦≡|∣∥–︱—︳╴¯ ̄﹉﹊﹍﹎﹋﹌﹏︴∕﹨╱╲/\"),
|
||||
])
|
||||
}
|
||||
|
|
|
@ -1,20 +1,27 @@
|
|||
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License).
|
||||
// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
// All possible vChewing-specific modifications are of:
|
||||
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
|
||||
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
1. The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor,
|
||||
except as required to fulfill notice requirements above.
|
||||
2. No trademark license is granted to use the trade names, trademarks, service
|
||||
marks, or product names of Contributor, except as required to fulfill notice
|
||||
requirements above.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
|
@ -45,17 +52,17 @@ import Cocoa
|
|||
}
|
||||
|
||||
// CharCodes: https://theasciicode.com.ar/ascii-control-characters/horizontal-tab-ascii-code-9.html
|
||||
enum CharCode: UInt/*16*/ {
|
||||
case yajuusenpai = 1145141919810893
|
||||
enum CharCode: UInt /*16*/ {
|
||||
case yajuusenpai = 1_145_141_919_810_893
|
||||
// - CharCode is not reliable at all. KeyCode is the most accurate. KeyCode doesn't give a phuque about the character sent through macOS keyboard layouts but only focuses on which physical key is pressed.
|
||||
}
|
||||
|
||||
class keyParser: NSObject {
|
||||
@objc private (set) var useVerticalMode: Bool
|
||||
@objc private (set) var inputText: String?
|
||||
@objc private (set) var inputTextIgnoringModifiers: String?
|
||||
@objc private (set) var charCode: UInt16
|
||||
@objc private (set) var keyCode: UInt16
|
||||
@objc private(set) var useVerticalMode: Bool
|
||||
@objc private(set) var inputText: String?
|
||||
@objc private(set) var inputTextIgnoringModifiers: String?
|
||||
@objc private(set) var charCode: UInt16
|
||||
@objc private(set) var keyCode: UInt16
|
||||
private var isFlagChanged: Bool
|
||||
private var flags: NSEvent.ModifierFlags
|
||||
private var cursorForwardKey: KeyCode
|
||||
|
@ -64,11 +71,15 @@ class keyParser: NSObject {
|
|||
private var extraChooseCandidateKeyReverse: KeyCode
|
||||
private var absorbedArrowKey: 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 inputTextIgnoringModifiers = AppleKeyboardConverter.cnvStringApple2ABC(inputTextIgnoringModifiers ?? inputText)
|
||||
let inputTextIgnoringModifiers = AppleKeyboardConverter.cnvStringApple2ABC(
|
||||
inputTextIgnoringModifiers ?? inputText)
|
||||
self.inputText = inputText
|
||||
self.inputTextIgnoringModifiers = inputTextIgnoringModifiers
|
||||
self.keyCode = keyCode
|
||||
|
@ -76,7 +87,8 @@ class keyParser: NSObject {
|
|||
self.flags = flags
|
||||
self.isFlagChanged = false
|
||||
useVerticalMode = isVerticalMode
|
||||
emacsKey = EmacsKeyHelper.detect(charCode: AppleKeyboardConverter.cnvApple2ABC(charCode), flags: flags)
|
||||
emacsKey = EmacsKeyHelper.detect(
|
||||
charCode: AppleKeyboardConverter.cnvApple2ABC(charCode), flags: flags)
|
||||
cursorForwardKey = useVerticalMode ? .down : .right
|
||||
cursorBackwardKey = useVerticalMode ? .up : .left
|
||||
extraChooseCandidateKey = useVerticalMode ? .left : .down
|
||||
|
@ -88,7 +100,8 @@ class keyParser: NSObject {
|
|||
|
||||
@objc init(event: NSEvent, isVerticalMode: Bool) {
|
||||
inputText = AppleKeyboardConverter.cnvStringApple2ABC(event.characters ?? "")
|
||||
inputTextIgnoringModifiers = AppleKeyboardConverter.cnvStringApple2ABC(event.charactersIgnoringModifiers ?? "")
|
||||
inputTextIgnoringModifiers = AppleKeyboardConverter.cnvStringApple2ABC(
|
||||
event.charactersIgnoringModifiers ?? "")
|
||||
keyCode = event.keyCode
|
||||
flags = event.modifierFlags
|
||||
isFlagChanged = (event.type == .flagsChanged) ? true : false
|
||||
|
@ -101,7 +114,8 @@ class keyParser: NSObject {
|
|||
return first
|
||||
}()
|
||||
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
|
||||
cursorBackwardKey = useVerticalMode ? .up : .left
|
||||
extraChooseCandidateKey = useVerticalMode ? .left : .down
|
||||
|
@ -114,8 +128,10 @@ class keyParser: NSObject {
|
|||
override var description: String {
|
||||
charCode = AppleKeyboardConverter.cnvApple2ABC(charCode)
|
||||
inputText = AppleKeyboardConverter.cnvStringApple2ABC(inputText ?? "")
|
||||
inputTextIgnoringModifiers = AppleKeyboardConverter.cnvStringApple2ABC(inputTextIgnoringModifiers ?? "")
|
||||
return "<\(super.description) inputText:\(String(describing: inputText)), inputTextIgnoringModifiers:\(String(describing: inputTextIgnoringModifiers)) charCode:\(charCode), keyCode:\(keyCode), flags:\(flags), cursorForwardKey:\(cursorForwardKey), cursorBackwardKey:\(cursorBackwardKey), extraChooseCandidateKey:\(extraChooseCandidateKey), extraChooseCandidateKeyReverse:\(extraChooseCandidateKeyReverse), absorbedArrowKey:\(absorbedArrowKey), verticalModeOnlyChooseCandidateKey:\(verticalModeOnlyChooseCandidateKey), emacsKey:\(emacsKey), useVerticalMode:\(useVerticalMode)>"
|
||||
inputTextIgnoringModifiers = AppleKeyboardConverter.cnvStringApple2ABC(
|
||||
inputTextIgnoringModifiers ?? "")
|
||||
return
|
||||
"<\(super.description) inputText:\(String(describing: inputText)), inputTextIgnoringModifiers:\(String(describing: inputTextIgnoringModifiers)) charCode:\(charCode), keyCode:\(keyCode), flags:\(flags), cursorForwardKey:\(cursorForwardKey), cursorBackwardKey:\(cursorBackwardKey), extraChooseCandidateKey:\(extraChooseCandidateKey), extraChooseCandidateKeyReverse:\(extraChooseCandidateKeyReverse), absorbedArrowKey:\(absorbedArrowKey), verticalModeOnlyChooseCandidateKey:\(verticalModeOnlyChooseCandidateKey), emacsKey:\(emacsKey), useVerticalMode:\(useVerticalMode)>"
|
||||
}
|
||||
|
||||
@objc var isShiftHold: Bool {
|
||||
|
@ -166,7 +182,8 @@ class keyParser: NSObject {
|
|||
}
|
||||
|
||||
@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 {
|
||||
|
@ -270,6 +287,6 @@ class EmacsKeyHelper: NSObject {
|
|||
if flags.contains(.control) {
|
||||
return vChewingEmacsKey(rawValue: charCode) ?? .none
|
||||
}
|
||||
return .none;
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,25 +1,32 @@
|
|||
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License).
|
||||
// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
// All possible vChewing-specific modifications are of:
|
||||
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
|
||||
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
1. The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor,
|
||||
except as required to fulfill notice requirements above.
|
||||
2. No trademark license is granted to use the trade names, trademarks, service
|
||||
marks, or product names of Contributor, except as required to fulfill notice
|
||||
requirements above.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
|
||||
public extension NSString {
|
||||
extension NSString {
|
||||
|
||||
/// Converts the index in an NSString to the index in a Swift string.
|
||||
///
|
||||
|
@ -29,7 +36,7 @@ public extension NSString {
|
|||
/// string have different lengths once the string contains such Emoji. The
|
||||
/// method helps to find the index in a Swift string by passing the index
|
||||
/// in an NSString.
|
||||
func characterIndex(from utf16Index:Int) -> (Int, String) {
|
||||
public func characterIndex(from utf16Index: Int) -> (Int, String) {
|
||||
let string = (self as String)
|
||||
var length = 0
|
||||
for (i, character) in string.enumerated() {
|
||||
|
@ -41,7 +48,7 @@ public extension NSString {
|
|||
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)
|
||||
if fixedIndex < string.count {
|
||||
fixedIndex += 1
|
||||
|
@ -49,7 +56,7 @@ public extension NSString {
|
|||
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)
|
||||
if fixedIndex > 0 {
|
||||
fixedIndex -= 1
|
||||
|
@ -57,11 +64,11 @@ public extension NSString {
|
|||
return string[..<string.index(string.startIndex, offsetBy: fixedIndex)].utf16.count
|
||||
}
|
||||
|
||||
@objc var count: Int {
|
||||
@objc public var count: Int {
|
||||
(self as String).count
|
||||
}
|
||||
|
||||
@objc func split() -> [NSString] {
|
||||
@objc public func split() -> [NSString] {
|
||||
Array(self as String).map {
|
||||
NSString(string: String($0))
|
||||
}
|
||||
|
|
|
@ -1,25 +1,31 @@
|
|||
// Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
|
||||
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
1. The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor,
|
||||
except as required to fulfill notice requirements above.
|
||||
2. No trademark license is granted to use the trade names, trademarks, service
|
||||
marks, or product names of Contributor, except as required to fulfill notice
|
||||
requirements above.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
|
||||
private extension String {
|
||||
mutating func selfReplace(_ strOf: String, _ strWith: String = "") {
|
||||
extension String {
|
||||
fileprivate mutating func selfReplace(_ strOf: String, _ strWith: String = "") {
|
||||
self = self.replacingOccurrences(of: strOf, with: strWith)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +1,27 @@
|
|||
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License).
|
||||
// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
// All possible vChewing-specific modifications are of:
|
||||
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
|
||||
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
1. The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor,
|
||||
except as required to fulfill notice requirements above.
|
||||
2. No trademark license is granted to use the trade names, trademarks, service
|
||||
marks, or product names of Contributor, except as required to fulfill notice
|
||||
requirements above.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
|
@ -23,7 +30,7 @@ public protocol FSEventStreamHelperDelegate: AnyObject {
|
|||
func helper(_ helper: FSEventStreamHelper, didReceive events: [FSEventStreamHelper.Event])
|
||||
}
|
||||
|
||||
public class FSEventStreamHelper : NSObject {
|
||||
public class FSEventStreamHelper: NSObject {
|
||||
|
||||
public struct Event {
|
||||
var path: String
|
||||
|
@ -48,17 +55,22 @@ public class FSEventStreamHelper : NSObject {
|
|||
}
|
||||
var context = FSEventStreamContext()
|
||||
context.info = Unmanaged.passUnretained(self).toOpaque()
|
||||
guard let stream = FSEventStreamCreate(nil, {
|
||||
guard
|
||||
let stream = FSEventStreamCreate(
|
||||
nil,
|
||||
{
|
||||
(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 pathsPtr = UnsafeBufferPointer(start: pathsBase, count: eventCount)
|
||||
let flagsPtr = UnsafeBufferPointer(start: eventFlags, count: eventCount)
|
||||
let eventIDsPtr = UnsafeBufferPointer(start: eventIds, count: eventCount)
|
||||
let events = (0..<eventCount).map {
|
||||
FSEventStreamHelper.Event(path: String(cString: pathsPtr[$0]),
|
||||
FSEventStreamHelper.Event(
|
||||
path: String(cString: pathsPtr[$0]),
|
||||
flags: flagsPtr[$0],
|
||||
id: eventIDsPtr[$0] )
|
||||
id: eventIDsPtr[$0])
|
||||
}
|
||||
helper.delegate?.helper(helper, didReceive: events)
|
||||
},
|
||||
|
@ -67,7 +79,8 @@ public class FSEventStreamHelper : NSObject {
|
|||
UInt64(kFSEventStreamEventIdSinceNow),
|
||||
1.0,
|
||||
FSEventStreamCreateFlags(kFSEventStreamCreateFlagNone)
|
||||
) else {
|
||||
)
|
||||
else {
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -89,4 +102,3 @@ public class FSEventStreamHelper : NSObject {
|
|||
self.stream = nil
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,26 +1,32 @@
|
|||
// Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
|
||||
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
1. The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor,
|
||||
except as required to fulfill notice requirements above.
|
||||
2. No trademark license is granted to use the trade names, trademarks, service
|
||||
marks, or product names of Contributor, except as required to fulfill notice
|
||||
requirements above.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
|
||||
@objc public class IME: NSObject {
|
||||
|
||||
static let dlgOpenPath = NSOpenPanel();
|
||||
static let dlgOpenPath = NSOpenPanel()
|
||||
|
||||
// MARK: - Print debug information to the console.
|
||||
@objc static func prtDebugIntel(_ strPrint: String) {
|
||||
|
@ -50,12 +56,15 @@ import Cocoa
|
|||
// MARK: - System Dark Mode Status Detector.
|
||||
@objc static func isDarkMode() -> Bool {
|
||||
if #available(macOS 10.15, *) {
|
||||
let appearanceDescription = NSApplication.shared.effectiveAppearance.debugDescription.lowercased()
|
||||
let appearanceDescription = NSApplication.shared.effectiveAppearance.debugDescription
|
||||
.lowercased()
|
||||
if appearanceDescription.contains("dark") {
|
||||
return true
|
||||
}
|
||||
} else if #available(macOS 10.14, *) {
|
||||
if let appleInterfaceStyle = UserDefaults.standard.object(forKey: "AppleInterfaceStyle") as? String {
|
||||
if let appleInterfaceStyle = UserDefaults.standard.object(forKey: "AppleInterfaceStyle")
|
||||
as? String
|
||||
{
|
||||
if appleInterfaceStyle.lowercased().contains("dark") {
|
||||
return true
|
||||
}
|
||||
|
@ -69,7 +78,8 @@ import Cocoa
|
|||
do {
|
||||
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 {
|
||||
NSLog("Item doesn't exist: \(path)")
|
||||
}
|
||||
|
@ -89,17 +99,29 @@ import Cocoa
|
|||
|
||||
let kTargetBin = "vChewing"
|
||||
let kTargetBundle = "/vChewing.app"
|
||||
let pathLibrary = isSudo ? "/Library" : FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask)[0].path
|
||||
let pathIMELibrary = isSudo ? "/Library/Input Methods" : FileManager.default.urls(for: .inputMethodsDirectory, in: .userDomainMask)[0].path
|
||||
let pathLibrary =
|
||||
isSudo
|
||||
? "/Library"
|
||||
: FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask)[0].path
|
||||
let pathIMELibrary =
|
||||
isSudo
|
||||
? "/Library/Input Methods"
|
||||
: FileManager.default.urls(for: .inputMethodsDirectory, in: .userDomainMask)[0].path
|
||||
let pathUnitKeyboardLayouts = "/Keyboard Layouts"
|
||||
let arrKeyLayoutFiles = ["/vChewing ETen.keylayout", "/vChewingKeyLayout.bundle", "/vChewing MiTAC.keylayout", "/vChewing IBM.keylayout", "/vChewing FakeSeigyou.keylayout", "/vChewing Dachen.keylayout"]
|
||||
let arrKeyLayoutFiles = [
|
||||
"/vChewing ETen.keylayout", "/vChewingKeyLayout.bundle", "/vChewing MiTAC.keylayout",
|
||||
"/vChewing IBM.keylayout", "/vChewing FakeSeigyou.keylayout",
|
||||
"/vChewing Dachen.keylayout",
|
||||
]
|
||||
|
||||
// 先移除各種鍵盤佈局。
|
||||
for objPath in arrKeyLayoutFiles {
|
||||
let objFullPath = pathLibrary + pathUnitKeyboardLayouts + objPath
|
||||
if !IME.trashTargetIfExists(objFullPath) { return -1 }
|
||||
}
|
||||
if CommandLine.arguments.count > 2 && CommandLine.arguments[2] == "--all" && CommandLine.arguments[1] == "uninstall" {
|
||||
if CommandLine.arguments.count > 2 && CommandLine.arguments[2] == "--all"
|
||||
&& CommandLine.arguments[1] == "uninstall"
|
||||
{
|
||||
// 再處理是否需要移除放在預設使用者資料夾內的檔案的情況。
|
||||
// 如果使用者有在輸入法偏好設定內將該目錄改到別的地方(而不是用 symbol link)的話,則不處理。
|
||||
// 目前暫時無法應對 symbol link 的情況。
|
||||
|
@ -127,12 +149,14 @@ import Cocoa
|
|||
var maybeInputSource = InputSourceHelper.inputSource(for: bundleID)
|
||||
|
||||
if maybeInputSource == nil {
|
||||
NSLog("Registering input source \(bundleID) at \(bundleUrl.absoluteString)");
|
||||
NSLog("Registering input source \(bundleID) at \(bundleUrl.absoluteString)")
|
||||
// then register
|
||||
let status = InputSourceHelper.registerTnputSource(at: bundleUrl)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -159,10 +183,12 @@ import Cocoa
|
|||
|
||||
if CommandLine.arguments.count > 2 && CommandLine.arguments[2] == "--all" {
|
||||
let enabled = InputSourceHelper.enableAllInputMode(for: bundleID)
|
||||
NSLog(enabled ? "All input sources enabled for \(bundleID)" : "Cannot enable all input sources for \(bundleID), but this is ignored")
|
||||
NSLog(
|
||||
enabled
|
||||
? "All input sources enabled for \(bundleID)"
|
||||
: "Cannot enable all input sources for \(bundleID), but this is ignored")
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -1,24 +1,31 @@
|
|||
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License).
|
||||
// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
// All possible vChewing-specific modifications are of:
|
||||
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
|
||||
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
1. The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor,
|
||||
except as required to fulfill notice requirements above.
|
||||
2. No trademark license is granted to use the trade names, trademarks, service
|
||||
marks, or product names of Contributor, except as required to fulfill notice
|
||||
requirements above.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
import Carbon
|
||||
import Cocoa
|
||||
|
||||
public class InputSourceHelper: NSObject {
|
||||
|
||||
|
@ -32,7 +39,9 @@ public class InputSourceHelper: NSObject {
|
|||
}
|
||||
|
||||
@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()
|
||||
for source in allInstalledInputSources() {
|
||||
if let propertyPtr = TISGetInputSourceProperty(source, propertyKey) {
|
||||
|
@ -74,7 +83,8 @@ public class InputSourceHelper: NSObject {
|
|||
var enabled = false
|
||||
for source in allInstalledInputSources() {
|
||||
guard let bundleIDPtr = TISGetInputSourceProperty(source, kTISPropertyBundleID),
|
||||
let _ = TISGetInputSourceProperty(source, kTISPropertyInputModeID) else {
|
||||
let _ = TISGetInputSourceProperty(source, kTISPropertyInputModeID)
|
||||
else {
|
||||
continue
|
||||
}
|
||||
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 {
|
||||
for source in allInstalledInputSources() {
|
||||
guard let bundleIDPtr = TISGetInputSourceProperty(source, kTISPropertyBundleID),
|
||||
let modePtr = TISGetInputSourceProperty(source, kTISPropertyInputModeID) else {
|
||||
let modePtr = TISGetInputSourceProperty(source, kTISPropertyInputModeID)
|
||||
else {
|
||||
continue
|
||||
}
|
||||
let inputsSourceBundleID = Unmanaged<CFString>.fromOpaque(bundleIDPtr).takeUnretainedValue()
|
||||
let inputsSourceBundleID = Unmanaged<CFString>.fromOpaque(bundleIDPtr)
|
||||
.takeUnretainedValue()
|
||||
let inputsSourceModeID = Unmanaged<CFString>.fromOpaque(modePtr).takeUnretainedValue()
|
||||
if modeID == String(inputsSourceModeID) && bundleID == String(inputsSourceBundleID) {
|
||||
let enabled = enable(inputSource: source)
|
||||
print("Attempt to enable input source of mode: \(modeID), bundle ID: \(bundleID), result: \(enabled)")
|
||||
print(
|
||||
"Attempt to enable input source of mode: \(modeID), bundle ID: \(bundleID), result: \(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).
|
||||
// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
// All possible vChewing-specific modifications are of:
|
||||
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
|
||||
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
1. The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor,
|
||||
except as required to fulfill notice requirements above.
|
||||
2. No trademark license is granted to use the trade names, trademarks, service
|
||||
marks, or product names of Contributor, except as required to fulfill notice
|
||||
requirements above.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
import InputMethodKit
|
||||
|
||||
private extension Bool {
|
||||
var state: NSControl.StateValue {
|
||||
extension Bool {
|
||||
fileprivate var state: NSControl.StateValue {
|
||||
self ? .on : .off
|
||||
}
|
||||
}
|
||||
|
@ -30,19 +37,19 @@ private let kMinKeyLabelSize: CGFloat = 10
|
|||
|
||||
private var gCurrentCandidateController: CandidateController?
|
||||
|
||||
private extension CandidateController {
|
||||
static let horizontal = HorizontalCandidateController()
|
||||
static let vertical = VerticalCandidateController()
|
||||
extension CandidateController {
|
||||
fileprivate static let horizontal = HorizontalCandidateController()
|
||||
fileprivate static let vertical = VerticalCandidateController()
|
||||
}
|
||||
|
||||
@objc(ctlInputMethod)
|
||||
class ctlInputMethod: IMKInputController {
|
||||
|
||||
@objc static let kIMEModeCHS = "org.atelierInmu.inputmethod.vChewing.IMECHS";
|
||||
@objc static let kIMEModeCHT = "org.atelierInmu.inputmethod.vChewing.IMECHT";
|
||||
@objc static let kIMEModeNULL = "org.atelierInmu.inputmethod.vChewing.IMENULL";
|
||||
@objc static let kIMEModeCHS = "org.atelierInmu.inputmethod.vChewing.IMECHS"
|
||||
@objc static let kIMEModeCHT = "org.atelierInmu.inputmethod.vChewing.IMECHT"
|
||||
@objc static let kIMEModeNULL = "org.atelierInmu.inputmethod.vChewing.IMENULL"
|
||||
|
||||
@objc static var areWeDeleting = false;
|
||||
@objc static var areWeDeleting = false
|
||||
|
||||
private static let tooltipController = TooltipController()
|
||||
|
||||
|
@ -80,66 +87,106 @@ class ctlInputMethod: IMKInputController {
|
|||
|
||||
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.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.state = mgrPrefs.cns11643Enabled.state
|
||||
|
||||
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.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.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.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.state = mgrPrefs.associatedPhrasesEnabled.state
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
menu.addItem(NSMenuItem.separator()) // ---------------------
|
||||
|
||||
menu.addItem(withTitle: NSLocalizedString("Open User Data Folder", comment: ""), action: #selector(openUserDataFolder(_:)), keyEquivalent: "")
|
||||
menu.addItem(withTitle: NSLocalizedString("Edit User Phrases…", comment: ""), action: #selector(openUserPhrases(_:)), keyEquivalent: "")
|
||||
menu.addItem(
|
||||
withTitle: NSLocalizedString("Open User Data Folder", comment: ""),
|
||||
action: #selector(openUserDataFolder(_:)), keyEquivalent: "")
|
||||
menu.addItem(
|
||||
withTitle: NSLocalizedString("Edit User Phrases…", comment: ""),
|
||||
action: #selector(openUserPhrases(_:)), keyEquivalent: "")
|
||||
|
||||
if optionKeyPressed {
|
||||
menu.addItem(withTitle: NSLocalizedString("Edit Excluded Phrases…", comment: ""), action: #selector(openExcludedPhrases(_:)), keyEquivalent: "")
|
||||
menu.addItem(withTitle: NSLocalizedString("Edit Phrase Replacement Table…", comment: ""), action: #selector(openPhraseReplacement(_:)), keyEquivalent: "")
|
||||
menu.addItem(withTitle: NSLocalizedString("Edit Associated Phrases…", comment: ""), action: #selector(openAssociatedPhrases(_:)), keyEquivalent: "")
|
||||
menu.addItem(withTitle: NSLocalizedString("Edit User Symbol & Emoji Data…", comment: ""), action: #selector(openUserSymbols(_:)), keyEquivalent: "")
|
||||
menu.addItem(
|
||||
withTitle: NSLocalizedString("Edit Excluded Phrases…", comment: ""),
|
||||
action: #selector(openExcludedPhrases(_:)), keyEquivalent: "")
|
||||
menu.addItem(
|
||||
withTitle: NSLocalizedString("Edit Phrase Replacement Table…", comment: ""),
|
||||
action: #selector(openPhraseReplacement(_:)), keyEquivalent: "")
|
||||
menu.addItem(
|
||||
withTitle: NSLocalizedString("Edit Associated Phrases…", comment: ""),
|
||||
action: #selector(openAssociatedPhrases(_:)), keyEquivalent: "")
|
||||
menu.addItem(
|
||||
withTitle: NSLocalizedString("Edit User Symbol & Emoji Data…", comment: ""),
|
||||
action: #selector(openUserSymbols(_:)), keyEquivalent: "")
|
||||
}
|
||||
|
||||
if (optionKeyPressed || !mgrPrefs.shouldAutoReloadUserDataFiles) {
|
||||
menu.addItem(withTitle: NSLocalizedString("Reload User Phrases", comment: ""), action: #selector(reloadUserPhrases(_:)), keyEquivalent: "")
|
||||
if optionKeyPressed || !mgrPrefs.shouldAutoReloadUserDataFiles {
|
||||
menu.addItem(
|
||||
withTitle: NSLocalizedString("Reload User Phrases", comment: ""),
|
||||
action: #selector(reloadUserPhrases(_:)), keyEquivalent: "")
|
||||
}
|
||||
|
||||
menu.addItem(NSMenuItem.separator()) // ---------------------
|
||||
|
||||
menu.addItem(withTitle: NSLocalizedString("vChewing Preferences…", comment: ""), action: #selector(showPreferences(_:)), keyEquivalent: "")
|
||||
menu.addItem(
|
||||
withTitle: NSLocalizedString("vChewing Preferences…", comment: ""),
|
||||
action: #selector(showPreferences(_:)), keyEquivalent: "")
|
||||
if !optionKeyPressed {
|
||||
menu.addItem(withTitle: NSLocalizedString("Check for Updates…", comment: ""), action: #selector(checkForUpdate(_:)), keyEquivalent: "")
|
||||
menu.addItem(
|
||||
withTitle: NSLocalizedString("Check for Updates…", comment: ""),
|
||||
action: #selector(checkForUpdate(_:)), keyEquivalent: "")
|
||||
}
|
||||
menu.addItem(withTitle: NSLocalizedString("Reboot vChewing…", comment: ""), action: #selector(selfTerminate(_:)), keyEquivalent: "")
|
||||
menu.addItem(withTitle: NSLocalizedString("About vChewing…", comment: ""), action: #selector(showAbout(_:)), keyEquivalent: "")
|
||||
menu.addItem(
|
||||
withTitle: NSLocalizedString("Reboot vChewing…", comment: ""),
|
||||
action: #selector(selfTerminate(_:)), keyEquivalent: "")
|
||||
menu.addItem(
|
||||
withTitle: NSLocalizedString("About vChewing…", comment: ""),
|
||||
action: #selector(showAbout(_:)), keyEquivalent: "")
|
||||
if optionKeyPressed {
|
||||
menu.addItem(withTitle: NSLocalizedString("Uninstall vChewing…", comment: ""), action: #selector(selfUninstall(_:)), keyEquivalent: "")
|
||||
menu.addItem(
|
||||
withTitle: NSLocalizedString("Uninstall vChewing…", comment: ""),
|
||||
action: #selector(selfUninstall(_:)), keyEquivalent: "")
|
||||
}
|
||||
|
||||
// NSMenu 會阻止任何 modified key 相關的訊號傳回輸入法,所以咱們在此重設鍵盤佈局
|
||||
|
@ -216,10 +263,14 @@ class ctlInputMethod: IMKInputController {
|
|||
ctlInputMethod.areWeDeleting = event.modifierFlags.contains([.shift, .command])
|
||||
|
||||
var textFrame = NSRect.zero
|
||||
let attributes: [AnyHashable: Any]? = (client as? IMKTextInput)?.attributes(forCharacterIndex: 0, lineHeightRectangle: &textFrame)
|
||||
let useVerticalMode = (attributes?["IMKTextOrientation"] as? NSNumber)?.intValue == 0 || false
|
||||
let attributes: [AnyHashable: Any]? = (client as? IMKTextInput)?.attributes(
|
||||
forCharacterIndex: 0, lineHeightRectangle: &textFrame)
|
||||
let useVerticalMode =
|
||||
(attributes?["IMKTextOrientation"] as? NSNumber)?.intValue == 0 || false
|
||||
|
||||
if (client as? IMKTextInput)?.bundleIdentifier() == "org.atelierInmu.vChewing.vChewingPhraseEditor" {
|
||||
if (client as? IMKTextInput)?.bundleIdentifier()
|
||||
== "org.atelierInmu.vChewing.vChewingPhraseEditor"
|
||||
{
|
||||
ctlInputMethod.areWeUsingOurOwnPhraseEditor = true
|
||||
} else {
|
||||
ctlInputMethod.areWeUsingOurOwnPhraseEditor = false
|
||||
|
@ -243,35 +294,77 @@ class ctlInputMethod: IMKInputController {
|
|||
}
|
||||
|
||||
@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?) {
|
||||
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?) {
|
||||
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?) {
|
||||
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?) {
|
||||
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?) {
|
||||
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?) {
|
||||
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?) {
|
||||
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?) {
|
||||
|
@ -289,8 +382,14 @@ class ctlInputMethod: IMKInputController {
|
|||
private func open(userFileAt path: String) {
|
||||
func checkIfUserFilesExist() -> Bool {
|
||||
if !mgrLangModel.checkIfUserLanguageModelFilesExist() {
|
||||
let content = String(format: NSLocalizedString("Please check the permission at \"%@\".", comment: ""), mgrLangModel.dataFolderPath(isDefaultFolder: false))
|
||||
ctlNonModalAlertWindow.shared.show(title: NSLocalizedString("Unable to create the user phrase file.", comment: ""), content: content, confirmButtonTitle: NSLocalizedString("OK", comment: ""), cancelButtonTitle: nil, cancelAsDefault: false, delegate: nil)
|
||||
let content = String(
|
||||
format: NSLocalizedString(
|
||||
"Please check the permission at \"%@\".", comment: ""),
|
||||
mgrLangModel.dataFolderPath(isDefaultFolder: false))
|
||||
ctlNonModalAlertWindow.shared.show(
|
||||
title: NSLocalizedString("Unable to create the user phrase file.", comment: ""),
|
||||
content: content, confirmButtonTitle: NSLocalizedString("OK", comment: ""),
|
||||
cancelButtonTitle: nil, cancelAsDefault: false, delegate: nil)
|
||||
NSApp.setActivationPolicy(.accessory)
|
||||
return false
|
||||
}
|
||||
|
@ -311,7 +410,8 @@ class ctlInputMethod: IMKInputController {
|
|||
if !mgrLangModel.checkIfUserDataFolderExists() {
|
||||
return
|
||||
}
|
||||
NSWorkspace.shared.openFile(mgrLangModel.dataFolderPath(isDefaultFolder: false), withApplication: "Finder")
|
||||
NSWorkspace.shared.openFile(
|
||||
mgrLangModel.dataFolderPath(isDefaultFolder: false), withApplication: "Finder")
|
||||
}
|
||||
|
||||
@objc func openExcludedPhrases(_ sender: Any?) {
|
||||
|
@ -393,7 +493,8 @@ extension ctlInputMethod {
|
|||
if buffer.isEmpty {
|
||||
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?) {
|
||||
|
@ -406,7 +507,9 @@ extension ctlInputMethod {
|
|||
if let previous = previous as? InputState.NotEmpty {
|
||||
commit(text: previous.composingBuffer, client: client)
|
||||
}
|
||||
(client as? IMKTextInput)?.setMarkedText("", selectionRange: NSMakeRange(0, 0), replacementRange: NSMakeRange(NSNotFound, NSNotFound))
|
||||
(client as? IMKTextInput)?.setMarkedText(
|
||||
"", selectionRange: NSMakeRange(0, 0),
|
||||
replacementRange: NSMakeRange(NSNotFound, NSNotFound))
|
||||
}
|
||||
|
||||
private func handle(state: InputState.Empty, previous: InputState, client: Any?) {
|
||||
|
@ -420,10 +523,14 @@ extension ctlInputMethod {
|
|||
if let previous = previous as? InputState.NotEmpty {
|
||||
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
|
||||
hideTooltip()
|
||||
|
||||
|
@ -431,7 +538,9 @@ extension ctlInputMethod {
|
|||
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?) {
|
||||
|
@ -446,7 +555,9 @@ extension ctlInputMethod {
|
|||
if !poppedText.isEmpty {
|
||||
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?) {
|
||||
|
@ -464,9 +575,13 @@ extension ctlInputMethod {
|
|||
|
||||
// the selection range is where the cursor is, with the length being 0 and replacement range NSNotFound,
|
||||
// i.e. the client app needs to take care of where to put this composing buffer
|
||||
client.setMarkedText(state.attributedString, selectionRange: NSMakeRange(Int(state.cursorIndex), 0), replacementRange: NSMakeRange(NSNotFound, NSNotFound))
|
||||
client.setMarkedText(
|
||||
state.attributedString, selectionRange: NSMakeRange(Int(state.cursorIndex), 0),
|
||||
replacementRange: NSMakeRange(NSNotFound, NSNotFound))
|
||||
if !state.tooltip.isEmpty {
|
||||
show(tooltip: state.tooltip, composingBuffer: state.composingBuffer, cursorIndex: state.cursorIndex, client: client)
|
||||
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,
|
||||
// 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 {
|
||||
hideTooltip()
|
||||
} 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,
|
||||
// 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)
|
||||
}
|
||||
|
||||
|
@ -507,7 +628,9 @@ extension ctlInputMethod {
|
|||
gCurrentCandidateController?.visible = false
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -563,21 +686,27 @@ extension ctlInputMethod {
|
|||
}
|
||||
|
||||
func candidateFont(name: String?, size: CGFloat) -> NSFont {
|
||||
let currentMUIFont = (keyHandler.inputMode == InputMode.imeModeCHS) ? "Sarasa Term Slab SC" : "Sarasa Term Slab TC"
|
||||
var finalReturnFont = NSFont(name: currentMUIFont, size: size) ?? NSFont.systemFont(ofSize: size)
|
||||
let currentMUIFont =
|
||||
(keyHandler.inputMode == InputMode.imeModeCHS)
|
||||
? "Sarasa Term Slab SC" : "Sarasa Term Slab TC"
|
||||
var finalReturnFont =
|
||||
NSFont(name: currentMUIFont, size: size) ?? NSFont.systemFont(ofSize: size)
|
||||
// 對更紗黑體的依賴到 macOS 11 Big Sur 為止。macOS 12 Monterey 開始則依賴系統內建的函數使用蘋方來處理。
|
||||
if #available(macOS 12.0, *) {finalReturnFont = NSFont.systemFont(ofSize: size)}
|
||||
if #available(macOS 12.0, *) { finalReturnFont = NSFont.systemFont(ofSize: size) }
|
||||
if let name = name {
|
||||
return NSFont(name: name, size: size) ?? finalReturnFont
|
||||
}
|
||||
return finalReturnFont
|
||||
}
|
||||
|
||||
gCurrentCandidateController?.keyLabelFont = labelFont(name: mgrPrefs.candidateKeyLabelFontName, size: keyLabelSize)
|
||||
gCurrentCandidateController?.candidateFont = candidateFont(name: mgrPrefs.candidateTextFontName, size: textSize)
|
||||
gCurrentCandidateController?.keyLabelFont = labelFont(
|
||||
name: mgrPrefs.candidateKeyLabelFontName, size: keyLabelSize)
|
||||
gCurrentCandidateController?.candidateFont = candidateFont(
|
||||
name: mgrPrefs.candidateTextFontName, size: textSize)
|
||||
|
||||
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 ? "^" : ""
|
||||
gCurrentCandidateController?.keyLabels = keyLabels.map {
|
||||
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 {
|
||||
(client as? IMKTextInput)?.attributes(forCharacterIndex: cursor, lineHeightRectangle: &lineHeightRect)
|
||||
(client as? IMKTextInput)?.attributes(
|
||||
forCharacterIndex: cursor, lineHeightRectangle: &lineHeightRect)
|
||||
cursor -= 1
|
||||
}
|
||||
|
||||
if useVerticalMode {
|
||||
gCurrentCandidateController?.set(windowTopLeftPoint: NSMakePoint(lineHeightRect.origin.x + lineHeightRect.size.width + 4.0, lineHeightRect.origin.y - 4.0), bottomOutOfScreenAdjustmentHeight: lineHeightRect.size.height + 4.0)
|
||||
gCurrentCandidateController?.set(
|
||||
windowTopLeftPoint: NSMakePoint(
|
||||
lineHeightRect.origin.x + lineHeightRect.size.width + 4.0,
|
||||
lineHeightRect.origin.y - 4.0),
|
||||
bottomOutOfScreenAdjustmentHeight: lineHeightRect.size.height + 4.0)
|
||||
} else {
|
||||
gCurrentCandidateController?.set(windowTopLeftPoint: NSMakePoint(lineHeightRect.origin.x, lineHeightRect.origin.y - 4.0), bottomOutOfScreenAdjustmentHeight: lineHeightRect.size.height + 4.0)
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
ctlInputMethod.tooltipController.show(tooltip: tooltip, at: lineHeightRect.origin)
|
||||
|
@ -642,22 +780,35 @@ extension ctlInputMethod: KeyHandlerDelegate {
|
|||
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 {
|
||||
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 {
|
||||
return false
|
||||
}
|
||||
if !state.validToWrite {
|
||||
return false
|
||||
}
|
||||
let InputModeReversed: InputMode = (keyHandler.inputMode == InputMode.imeModeCHT) ? InputMode.imeModeCHS : InputMode.imeModeCHT
|
||||
mgrLangModel.writeUserPhrase(state.userPhrase, inputMode: keyHandler.inputMode, areWeDuplicating: state.chkIfUserPhraseExists, areWeDeleting: ctlInputMethod.areWeDeleting)
|
||||
mgrLangModel.writeUserPhrase(state.userPhraseConverted, inputMode: InputModeReversed, areWeDuplicating: false, areWeDeleting: ctlInputMethod.areWeDeleting)
|
||||
let refInputModeReversed: InputMode =
|
||||
(keyHandler.inputMode == InputMode.imeModeCHT)
|
||||
? InputMode.imeModeCHS : InputMode.imeModeCHT
|
||||
mgrLangModel.writeUserPhrase(
|
||||
state.userPhrase, inputMode: keyHandler.inputMode,
|
||||
areWeDuplicating: state.chkIfUserPhraseExists,
|
||||
areWeDeleting: ctlInputMethod.areWeDeleting)
|
||||
mgrLangModel.writeUserPhrase(
|
||||
state.userPhraseConverted, inputMode: refInputModeReversed,
|
||||
areWeDuplicating: false,
|
||||
areWeDeleting: ctlInputMethod.areWeDeleting)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -674,7 +825,9 @@ extension ctlInputMethod: CandidateControllerDelegate {
|
|||
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 {
|
||||
return state.candidates[Int(index)]
|
||||
} else if let state = state as? InputState.AssociatedPhrases {
|
||||
|
@ -683,13 +836,18 @@ extension ctlInputMethod: CandidateControllerDelegate {
|
|||
return ""
|
||||
}
|
||||
|
||||
func candidateController(_ controller: CandidateController, didSelectCandidateAtIndex index: UInt) {
|
||||
func candidateController(
|
||||
_ controller: CandidateController, didSelectCandidateAtIndex index: UInt
|
||||
) {
|
||||
let client = currentCandidateClient
|
||||
|
||||
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 {
|
||||
self.handle(state: .SymbolTable(node: node, useVerticalMode: state.useVerticalMode), client: currentCandidateClient)
|
||||
self.handle(
|
||||
state: .SymbolTable(node: node, useVerticalMode: state.useVerticalMode),
|
||||
client: currentCandidateClient)
|
||||
} else {
|
||||
self.handle(state: .Committing(poppedText: node.title), client: client)
|
||||
self.handle(state: .Empty(), client: client)
|
||||
|
@ -710,7 +868,10 @@ extension ctlInputMethod: CandidateControllerDelegate {
|
|||
let composingBuffer = inputting.composingBuffer
|
||||
handle(state: .Committing(poppedText: composingBuffer), client: client)
|
||||
if mgrPrefs.associatedPhrasesEnabled,
|
||||
let associatePhrases = keyHandler.buildAssociatePhraseState(withKey: composingBuffer, useVerticalMode: state.useVerticalMode) as? InputState.AssociatedPhrases {
|
||||
let associatePhrases = keyHandler.buildAssociatePhraseState(
|
||||
withKey: composingBuffer, useVerticalMode: state.useVerticalMode)
|
||||
as? InputState.AssociatedPhrases
|
||||
{
|
||||
self.handle(state: associatePhrases, client: client)
|
||||
} else {
|
||||
handle(state: .Empty(), client: client)
|
||||
|
@ -725,7 +886,10 @@ extension ctlInputMethod: CandidateControllerDelegate {
|
|||
let selectedValue = state.candidates[Int(index)]
|
||||
handle(state: .Committing(poppedText: selectedValue), client: currentCandidateClient)
|
||||
if mgrPrefs.associatedPhrasesEnabled,
|
||||
let associatePhrases = keyHandler.buildAssociatePhraseState(withKey: selectedValue, useVerticalMode: state.useVerticalMode) as? InputState.AssociatedPhrases {
|
||||
let associatePhrases = keyHandler.buildAssociatePhraseState(
|
||||
withKey: selectedValue, useVerticalMode: state.useVerticalMode)
|
||||
as? InputState.AssociatedPhrases
|
||||
{
|
||||
self.handle(state: associatePhrases, client: client)
|
||||
} else {
|
||||
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).
|
||||
// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
// All possible vChewing-specific modifications are of:
|
||||
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
|
||||
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
1. The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor,
|
||||
except as required to fulfill notice requirements above.
|
||||
2. No trademark license is granted to use the trade names, trademarks, service
|
||||
marks, or product names of Contributor, except as required to fulfill notice
|
||||
requirements above.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
|
@ -153,13 +160,13 @@ struct ComposingBufferSize {
|
|||
case eten = 1
|
||||
case hsu = 2
|
||||
case eten26 = 3
|
||||
case IBM = 4
|
||||
case MiTAC = 5
|
||||
case FakeSeigyou = 6
|
||||
case ibm = 4
|
||||
case mitac = 5
|
||||
case fakeSeigyou = 6
|
||||
case hanyuPinyin = 10
|
||||
|
||||
var name: String {
|
||||
switch (self) {
|
||||
switch self {
|
||||
case .standard:
|
||||
return "Standard"
|
||||
case .eten:
|
||||
|
@ -168,11 +175,11 @@ struct ComposingBufferSize {
|
|||
return "Hsu"
|
||||
case .eten26:
|
||||
return "ETen26"
|
||||
case .IBM:
|
||||
case .ibm:
|
||||
return "IBM"
|
||||
case .MiTAC:
|
||||
case .mitac:
|
||||
return "MiTAC"
|
||||
case .FakeSeigyou:
|
||||
case .fakeSeigyou:
|
||||
return "FakeSeigyou"
|
||||
case .hanyuPinyin:
|
||||
return "HanyuPinyin"
|
||||
|
@ -182,8 +189,9 @@ struct ComposingBufferSize {
|
|||
|
||||
// MARK: -
|
||||
@objc public class mgrPrefs: NSObject {
|
||||
static var allKeys:[String] {
|
||||
[kIsDebugModeEnabled,
|
||||
static var allKeys: [String] {
|
||||
[
|
||||
kIsDebugModeEnabled,
|
||||
kUserDataFolderSpecified,
|
||||
kKeyboardLayoutPreference,
|
||||
kBasisKeyboardLayoutPreference,
|
||||
|
@ -211,10 +219,11 @@ struct ComposingBufferSize {
|
|||
kUseSCPCTypingMode,
|
||||
kMaxCandidateLength,
|
||||
kShouldNotFartInLieuOfBeep,
|
||||
kAssociatedPhrasesEnabled]
|
||||
kAssociatedPhrasesEnabled,
|
||||
]
|
||||
}
|
||||
|
||||
@objc public static func setMissingDefaults () {
|
||||
@objc public static func setMissingDefaults() {
|
||||
// 既然 Preferences Module 的預設屬性不自動寫入 plist、而且還是 private,那這邊就先寫入了。
|
||||
|
||||
// 首次啟用輸入法時不要啟用偵錯模式。
|
||||
|
@ -229,7 +238,9 @@ struct ComposingBufferSize {
|
|||
|
||||
// 預設顯示選字窗翻頁按鈕
|
||||
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 剛剛好
|
||||
if UserDefaults.standard.object(forKey: kCandidateListTextSize) == nil {
|
||||
UserDefaults.standard.set(mgrPrefs.candidateListTextSize, forKey: kCandidateListTextSize)
|
||||
UserDefaults.standard.set(
|
||||
mgrPrefs.candidateListTextSize, forKey: kCandidateListTextSize)
|
||||
}
|
||||
|
||||
// 預設摁空格鍵來選字,所以設成 true
|
||||
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 {
|
||||
UserDefaults.standard.set(mgrPrefs.shouldAutoReloadUserDataFiles, forKey: kShouldAutoReloadUserDataFiles)
|
||||
UserDefaults.standard.set(
|
||||
mgrPrefs.shouldAutoReloadUserDataFiles, forKey: kShouldAutoReloadUserDataFiles)
|
||||
}
|
||||
|
||||
// 預設情況下讓 Tab 鍵在選字窗內切換候選字、而不是用來換頁。
|
||||
if UserDefaults.standard.object(forKey: kSpecifyTabKeyBehavior) == nil {
|
||||
UserDefaults.standard.set(mgrPrefs.specifyTabKeyBehavior, forKey: kSpecifyTabKeyBehavior)
|
||||
UserDefaults.standard.set(
|
||||
mgrPrefs.specifyTabKeyBehavior, forKey: kSpecifyTabKeyBehavior)
|
||||
}
|
||||
|
||||
// 預設情況下讓 Space 鍵在選字窗內切換候選字、而不是用來換頁。
|
||||
if UserDefaults.standard.object(forKey: kSpecifySpaceKeyBehavior) == nil {
|
||||
UserDefaults.standard.set(mgrPrefs.specifySpaceKeyBehavior, forKey: kSpecifySpaceKeyBehavior)
|
||||
UserDefaults.standard.set(
|
||||
mgrPrefs.specifySpaceKeyBehavior, forKey: kSpecifySpaceKeyBehavior)
|
||||
}
|
||||
|
||||
// 預設禁用逐字選字模式(就是每個字都要選的那種),所以設成 false
|
||||
|
@ -269,22 +285,30 @@ struct ComposingBufferSize {
|
|||
|
||||
// 預設禁用逐字選字模式時的聯想詞功能,所以設成 false
|
||||
if UserDefaults.standard.object(forKey: kAssociatedPhrasesEnabled) == nil {
|
||||
UserDefaults.standard.set(mgrPrefs.associatedPhrasesEnabled, forKey: kAssociatedPhrasesEnabled)
|
||||
UserDefaults.standard.set(
|
||||
mgrPrefs.associatedPhrasesEnabled, forKey: kAssociatedPhrasesEnabled)
|
||||
}
|
||||
|
||||
// 預設漢音風格選字,所以要設成 0
|
||||
if UserDefaults.standard.object(forKey: kSelectPhraseAfterCursorAsCandidatePreference) == nil {
|
||||
UserDefaults.standard.set(mgrPrefs.selectPhraseAfterCursorAsCandidate, forKey: kSelectPhraseAfterCursorAsCandidatePreference)
|
||||
if UserDefaults.standard.object(forKey: kSelectPhraseAfterCursorAsCandidatePreference)
|
||||
== nil
|
||||
{
|
||||
UserDefaults.standard.set(
|
||||
mgrPrefs.selectPhraseAfterCursorAsCandidate,
|
||||
forKey: kSelectPhraseAfterCursorAsCandidatePreference)
|
||||
}
|
||||
|
||||
// 預設在選字後自動移動游標
|
||||
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 {
|
||||
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 {
|
||||
UserDefaults.standard.set(mgrPrefs.chineseConversionEnabled, forKey: kChineseConversionEnabled)
|
||||
UserDefaults.standard.set(
|
||||
mgrPrefs.chineseConversionEnabled, forKey: kChineseConversionEnabled)
|
||||
}
|
||||
|
||||
// 預設停用繁體轉 JIS 當用新字體模組
|
||||
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 {
|
||||
UserDefaults.standard.set(mgrPrefs.phraseReplacementEnabled, forKey: kPhraseReplacementEnabled)
|
||||
UserDefaults.standard.set(
|
||||
mgrPrefs.phraseReplacementEnabled, forKey: kPhraseReplacementEnabled)
|
||||
}
|
||||
|
||||
// 預設沒事不要在那裡放屁
|
||||
if UserDefaults.standard.object(forKey: kShouldNotFartInLieuOfBeep) == nil {
|
||||
UserDefaults.standard.set(mgrPrefs.shouldNotFartInLieuOfBeep, forKey: kShouldNotFartInLieuOfBeep)
|
||||
UserDefaults.standard.set(
|
||||
mgrPrefs.shouldNotFartInLieuOfBeep, forKey: kShouldNotFartInLieuOfBeep)
|
||||
}
|
||||
|
||||
UserDefaults.standard.synchronize()
|
||||
|
@ -326,7 +354,7 @@ struct ComposingBufferSize {
|
|||
}
|
||||
|
||||
@UserDefault(key: kAppleLanguagesPreferences, defaultValue: [])
|
||||
@objc static var appleLanguages: Array<String>
|
||||
@objc static var appleLanguages: [String]
|
||||
|
||||
@UserDefault(key: kKeyboardLayoutPreference, defaultValue: 0)
|
||||
@objc static var keyboardLayout: Int
|
||||
|
@ -335,7 +363,8 @@ struct ComposingBufferSize {
|
|||
(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
|
||||
|
||||
@UserDefault(key: kShowPageButtonsInCandidateWindow, defaultValue: true)
|
||||
|
@ -411,7 +440,8 @@ struct ComposingBufferSize {
|
|||
// 康熙轉換與 JIS 轉換不能同時開啟,否則會出現某些奇奇怪怪的情況
|
||||
if chineseConversionEnabled && shiftJISShinjitaiOutputEnabled {
|
||||
self.toggleShiftJISShinjitaiOutputEnabled()
|
||||
UserDefaults.standard.set(shiftJISShinjitaiOutputEnabled, forKey: kShiftJISShinjitaiOutputEnabled)
|
||||
UserDefaults.standard.set(
|
||||
shiftJISShinjitaiOutputEnabled, forKey: kShiftJISShinjitaiOutputEnabled)
|
||||
}
|
||||
UserDefaults.standard.set(chineseConversionEnabled, forKey: kChineseConversionEnabled)
|
||||
return chineseConversionEnabled
|
||||
|
@ -423,8 +453,11 @@ struct ComposingBufferSize {
|
|||
@objc @discardableResult static func toggleShiftJISShinjitaiOutputEnabled() -> Bool {
|
||||
shiftJISShinjitaiOutputEnabled = !shiftJISShinjitaiOutputEnabled
|
||||
// 康熙轉換與 JIS 轉換不能同時開啟,否則會出現某些奇奇怪怪的情況
|
||||
if shiftJISShinjitaiOutputEnabled && chineseConversionEnabled {self.toggleChineseConversionEnabled()}
|
||||
UserDefaults.standard.set(shiftJISShinjitaiOutputEnabled, forKey: kShiftJISShinjitaiOutputEnabled)
|
||||
if shiftJISShinjitaiOutputEnabled && chineseConversionEnabled {
|
||||
self.toggleChineseConversionEnabled()
|
||||
}
|
||||
UserDefaults.standard.set(
|
||||
shiftJISShinjitaiOutputEnabled, forKey: kShiftJISShinjitaiOutputEnabled)
|
||||
return shiftJISShinjitaiOutputEnabled
|
||||
}
|
||||
|
||||
|
@ -439,7 +472,6 @@ struct ComposingBufferSize {
|
|||
@UserDefault(key: kEscToCleanInputBuffer, defaultValue: true)
|
||||
@objc static var escToCleanInputBuffer: Bool
|
||||
|
||||
|
||||
@UserDefault(key: kSpecifyTabKeyBehavior, defaultValue: false)
|
||||
@objc static var specifyTabKeyBehavior: Bool
|
||||
|
||||
|
@ -499,13 +531,16 @@ struct ComposingBufferSize {
|
|||
case .empty:
|
||||
return NSLocalizedString("Candidates keys cannot be empty.", comment: "")
|
||||
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:
|
||||
return NSLocalizedString("Candidate keys cannot contain space.", comment: "")
|
||||
case .duplicatedCharacters:
|
||||
return NSLocalizedString("There should not be duplicated keys.", comment: "")
|
||||
case .tooShort:
|
||||
return NSLocalizedString("Please specify at least 4 candidate keys.", comment: "")
|
||||
return NSLocalizedString(
|
||||
"Please specify at least 4 candidate keys.", comment: "")
|
||||
case .tooLong:
|
||||
return NSLocalizedString("Maximum 15 candidate keys allowed.", comment: "")
|
||||
}
|
||||
|
|
|
@ -235,7 +235,7 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing
|
|||
[currentMarkedPhrase appendString:userPhrase];
|
||||
if (areWeDuplicating && !areWeDeleting) {
|
||||
// Do not use ASCII characters to comment here.
|
||||
// Otherwise, it will be scrambled by HYPY2BPMF module shipped in the vChewing Phrase Editor.
|
||||
// Otherwise, it will be scrambled by cnvHYPYtoBPMF module shipped in the vChewing Phrase Editor.
|
||||
[currentMarkedPhrase appendString:@"\t#𝙾𝚟𝚎𝚛𝚛𝚒𝚍𝚎"];
|
||||
}
|
||||
[currentMarkedPhrase appendString:@"\n"];
|
||||
|
|
|
@ -1,27 +1,33 @@
|
|||
// Copyright (c) 2022 and onwards Isaac Xen (MIT License).
|
||||
// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
|
||||
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
1. The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor,
|
||||
except as required to fulfill notice requirements above.
|
||||
2. No trademark license is granted to use the trade names, trademarks, service
|
||||
marks, or product names of Contributor, except as required to fulfill notice
|
||||
requirements above.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
|
||||
@objc public class clsSFX: NSObject, NSSoundDelegate {
|
||||
private static let shared = clsSFX()
|
||||
private override init(){
|
||||
private override init() {
|
||||
super.init()
|
||||
}
|
||||
private var currentBeep: NSSound?
|
||||
|
@ -33,14 +39,14 @@ import Cocoa
|
|||
}
|
||||
}
|
||||
// Create a new beep sound if possible
|
||||
var sndBeep:String
|
||||
var sndBeep: String
|
||||
if mgrPrefs.shouldNotFartInLieuOfBeep == false {
|
||||
sndBeep = "Fart"
|
||||
} else {
|
||||
sndBeep = "Beep"
|
||||
}
|
||||
guard
|
||||
let beep = NSSound(named:sndBeep)
|
||||
let beep = NSSound(named: sndBeep)
|
||||
else {
|
||||
NSSound.beep()
|
||||
return
|
||||
|
|
|
@ -1,20 +1,27 @@
|
|||
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License).
|
||||
// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
// All possible vChewing-specific modifications are of:
|
||||
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
|
||||
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
1. The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor,
|
||||
except as required to fulfill notice requirements above.
|
||||
2. No trademark license is granted to use the trade names, trademarks, service
|
||||
marks, or product names of Contributor, except as required to fulfill notice
|
||||
requirements above.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
|
@ -34,7 +41,7 @@ if CommandLine.arguments.count > 1 {
|
|||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -44,7 +51,9 @@ if !loaded {
|
|||
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).")
|
||||
exit(-1)
|
||||
}
|
||||
|
|
|
@ -1,20 +1,27 @@
|
|||
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License).
|
||||
// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
// All possible vChewing-specific modifications are of:
|
||||
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
|
||||
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
1. The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor,
|
||||
except as required to fulfill notice requirements above.
|
||||
2. No trademark license is granted to use the trade names, trademarks, service
|
||||
marks, or product names of Contributor, except as required to fulfill notice
|
||||
requirements above.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
|
@ -34,8 +41,10 @@ public class CandidateKeyLabel: NSObject {
|
|||
@objc(VTCandidateControllerDelegate)
|
||||
public protocol CandidateControllerDelegate: AnyObject {
|
||||
func candidateCountForController(_ controller: CandidateController) -> UInt
|
||||
func candidateController(_ controller: CandidateController, candidateAtIndex index: UInt) -> String
|
||||
func candidateController(_ controller: CandidateController, didSelectCandidateAtIndex index: UInt)
|
||||
func candidateController(_ controller: CandidateController, candidateAtIndex index: UInt)
|
||||
-> String
|
||||
func candidateController(
|
||||
_ controller: CandidateController, didSelectCandidateAtIndex index: UInt)
|
||||
}
|
||||
|
||||
@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)
|
||||
}
|
||||
@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 tooltip: String = ""
|
||||
|
||||
|
@ -113,7 +124,8 @@ public class CandidateController: NSWindowController {
|
|||
@objc(setWindowTopLeftPoint:bottomOutOfScreenAdjustmentHeight:)
|
||||
public func set(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight height: CGFloat) {
|
||||
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
|
||||
for screen in NSScreen.screens {
|
||||
let frame = screen.visibleFrame
|
||||
if windowTopLeftPoint.x >= frame.minX &&
|
||||
windowTopLeftPoint.x <= frame.maxX &&
|
||||
windowTopLeftPoint.y >= frame.minY &&
|
||||
windowTopLeftPoint.y <= frame.maxY {
|
||||
if windowTopLeftPoint.x >= frame.minX && windowTopLeftPoint.x <= frame.maxX
|
||||
&& windowTopLeftPoint.y >= frame.minY && windowTopLeftPoint.y <= frame.maxY
|
||||
{
|
||||
screenFrame = frame
|
||||
break
|
||||
}
|
||||
|
|
|
@ -1,25 +1,32 @@
|
|||
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License).
|
||||
// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
// All possible vChewing-specific modifications are of:
|
||||
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
|
||||
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
1. The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor,
|
||||
except as required to fulfill notice requirements above.
|
||||
2. No trademark license is granted to use the trade names, trademarks, service
|
||||
marks, or product names of Contributor, except as required to fulfill notice
|
||||
requirements above.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
|
||||
fileprivate class HorizontalCandidateView: NSView {
|
||||
private class HorizontalCandidateView: NSView {
|
||||
var highlightedIndex: UInt = 0
|
||||
var action: Selector?
|
||||
weak var target: AnyObject?
|
||||
|
@ -57,12 +64,14 @@ fileprivate class HorizontalCandidateView: NSView {
|
|||
let count = min(labels.count, candidates.count)
|
||||
keyLabels = Array(labels[0..<count])
|
||||
displayedCandidates = Array(candidates[0..<count])
|
||||
dispCandidatesWithLabels = zip(keyLabels,displayedCandidates).map() {$0 + $1}
|
||||
dispCandidatesWithLabels = zip(keyLabels, displayedCandidates).map { $0 + $1 }
|
||||
|
||||
var newWidths = [CGFloat]()
|
||||
let baseSize = NSSize(width: 10240.0, height: 10240.0)
|
||||
for index in 0..<count {
|
||||
let rctCandidate = (dispCandidatesWithLabels[index] as NSString).boundingRect(with: baseSize, options: .usesLineFragmentOrigin, attributes: candidateWithLabelAttrDict)
|
||||
let rctCandidate = (dispCandidatesWithLabels[index] as NSString).boundingRect(
|
||||
with: baseSize, options: .usesLineFragmentOrigin,
|
||||
attributes: candidateWithLabelAttrDict)
|
||||
var cellWidth = rctCandidate.size.width + cellPadding
|
||||
let cellHeight = rctCandidate.size.height + cellPadding
|
||||
if cellWidth < cellHeight * 1.35 {
|
||||
|
@ -79,17 +88,24 @@ fileprivate class HorizontalCandidateView: NSView {
|
|||
paraStyle.setParagraphStyle(NSParagraphStyle.default)
|
||||
paraStyle.alignment = .center
|
||||
|
||||
candidateWithLabelAttrDict = [.font: candidateFont,
|
||||
candidateWithLabelAttrDict = [
|
||||
.font: candidateFont,
|
||||
.paragraphStyle: paraStyle,
|
||||
.foregroundColor: NSColor.labelColor] // We still need this dummy section to make sure the space occupations of the candidates are correct.
|
||||
.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,
|
||||
.verticalGlyphForm: true as AnyObject,
|
||||
.foregroundColor: NSColor.secondaryLabelColor] // Candidate phrase text color
|
||||
candidateAttrDict = [.font: candidateFont,
|
||||
.foregroundColor: NSColor.secondaryLabelColor,
|
||||
] // Candidate phrase text color
|
||||
candidateAttrDict = [
|
||||
.font: candidateFont,
|
||||
.paragraphStyle: paraStyle,
|
||||
.foregroundColor: NSColor.labelColor] // Candidate index text color
|
||||
.foregroundColor: NSColor.labelColor,
|
||||
] // Candidate index text color
|
||||
let labelFontSize = labelFont.pointSize
|
||||
let candidateFontSize = candidateFont.pointSize
|
||||
let biggestSize = max(labelFontSize, candidateFontSize)
|
||||
|
@ -106,14 +122,23 @@ fileprivate class HorizontalCandidateView: NSView {
|
|||
|
||||
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
|
||||
for index in 0..<elementWidths.count {
|
||||
let currentWidth = elementWidths[index]
|
||||
let rctCandidateArea = NSRect(x: accuWidth, y: 0.0, width: currentWidth + 1.0, height: candidateTextHeight + cellPadding)
|
||||
let rctLabel = NSRect(x: accuWidth + cellPadding / 2 - 1, y: cellPadding / 2 , width: keyLabelWidth, height: keyLabelHeight * 2.0)
|
||||
let rctCandidatePhrase = NSRect(x: accuWidth + keyLabelWidth - 1, y: cellPadding / 2 , width: currentWidth - keyLabelWidth, height: candidateTextHeight)
|
||||
let rctCandidateArea = NSRect(
|
||||
x: accuWidth, y: 0.0, width: currentWidth + 1.0,
|
||||
height: candidateTextHeight + cellPadding)
|
||||
let rctLabel = NSRect(
|
||||
x: accuWidth + cellPadding / 2 - 1, y: cellPadding / 2, width: keyLabelWidth,
|
||||
height: keyLabelHeight * 2.0)
|
||||
let rctCandidatePhrase = NSRect(
|
||||
x: accuWidth + keyLabelWidth - 1, y: cellPadding / 2,
|
||||
width: currentWidth - keyLabelWidth,
|
||||
height: candidateTextHeight)
|
||||
|
||||
var activeCandidateIndexAttr = keyLabelAttrDict
|
||||
var activeCandidateAttr = candidateAttrDict
|
||||
|
@ -122,28 +147,43 @@ fileprivate class HorizontalCandidateView: NSView {
|
|||
// The background color of the highlightened candidate
|
||||
switch ctlInputMethod.currentKeyHandler.inputMode {
|
||||
case InputMode.imeModeCHS:
|
||||
NSColor.systemRed.blended(withFraction: colorBlendAmount, of: NSColor.controlBackgroundColor)!.setFill()
|
||||
NSColor.systemRed.blended(
|
||||
withFraction: colorBlendAmount,
|
||||
of: NSColor.controlBackgroundColor)!
|
||||
.setFill()
|
||||
case InputMode.imeModeCHT:
|
||||
NSColor.systemBlue.blended(withFraction: colorBlendAmount, of: NSColor.controlBackgroundColor)!.setFill()
|
||||
NSColor.systemBlue.blended(
|
||||
withFraction: colorBlendAmount,
|
||||
of: NSColor.controlBackgroundColor)!
|
||||
.setFill()
|
||||
default:
|
||||
NSColor.alternateSelectedControlColor.setFill()
|
||||
}
|
||||
activeCandidateIndexAttr[.foregroundColor] = NSColor.selectedMenuItemTextColor.withAlphaComponent(0.84) // The index text color of the highlightened candidate
|
||||
activeCandidateAttr[.foregroundColor] = NSColor.selectedMenuItemTextColor // The phrase text color of the highlightened candidate
|
||||
// Highlightened index text color
|
||||
activeCandidateIndexAttr[.foregroundColor] = NSColor.selectedMenuItemTextColor
|
||||
.withAlphaComponent(0.84)
|
||||
// Highlightened phrase text color
|
||||
activeCandidateAttr[.foregroundColor] = NSColor.selectedMenuItemTextColor
|
||||
} else {
|
||||
NSColor.controlBackgroundColor.setFill()
|
||||
}
|
||||
switch ctlInputMethod.currentKeyHandler.inputMode {
|
||||
case InputMode.imeModeCHS:
|
||||
if #available(macOS 12.0, *) {activeCandidateAttr[.languageIdentifier] = "zh-Hans" as AnyObject}
|
||||
if #available(macOS 12.0, *) {
|
||||
activeCandidateAttr[.languageIdentifier] = "zh-Hans" as AnyObject
|
||||
}
|
||||
case InputMode.imeModeCHT:
|
||||
if #available(macOS 12.0, *) {activeCandidateAttr[.languageIdentifier] = "zh-Hant" as AnyObject}
|
||||
if #available(macOS 12.0, *) {
|
||||
activeCandidateAttr[.languageIdentifier] = "zh-Hant" as AnyObject
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
NSBezierPath.fill(rctCandidateArea)
|
||||
(keyLabels[index] as NSString).draw(in: rctLabel, withAttributes: activeCandidateIndexAttr)
|
||||
(displayedCandidates[index] as NSString).draw(in: rctCandidatePhrase, withAttributes: activeCandidateAttr)
|
||||
(keyLabels[index] as NSString).draw(
|
||||
in: rctLabel, withAttributes: activeCandidateIndexAttr)
|
||||
(displayedCandidates[index] as NSString).draw(
|
||||
in: rctCandidatePhrase, withAttributes: activeCandidateAttr)
|
||||
accuWidth += currentWidth + 1.0
|
||||
}
|
||||
}
|
||||
|
@ -206,7 +246,8 @@ public class HorizontalCandidateController: CandidateController {
|
|||
public init() {
|
||||
var contentRect = NSRect(x: 128.0, y: 128.0, width: 0.0, height: 0.0)
|
||||
let styleMask: NSWindow.StyleMask = [.nonactivatingPanel]
|
||||
let panel = NSPanel(contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false)
|
||||
let panel = NSPanel(
|
||||
contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false)
|
||||
panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel) + 1)
|
||||
panel.hasShadow = true
|
||||
panel.isOpaque = false
|
||||
|
@ -216,7 +257,8 @@ public class HorizontalCandidateController: CandidateController {
|
|||
candidateView = HorizontalCandidateView(frame: contentRect)
|
||||
|
||||
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
|
||||
if #available(macOS 10.13, *) {
|
||||
candidateView.layer?.cornerRadius = 6.0
|
||||
|
@ -225,7 +267,7 @@ public class HorizontalCandidateController: CandidateController {
|
|||
panel.contentView?.addSubview(candidateView)
|
||||
|
||||
contentRect.size = NSSize(width: 20.0, height: 10.0) // Reduce the button width
|
||||
let buttonAttribute: [NSAttributedString.Key : Any] = [.font : NSFont.systemFont(ofSize: 9.0)]
|
||||
let buttonAttribute: [NSAttributedString.Key: Any] = [.font: NSFont.systemFont(ofSize: 9.0)]
|
||||
|
||||
nextPageButton = NSButton(frame: contentRect)
|
||||
NSColor.controlBackgroundColor.setFill()
|
||||
|
@ -237,7 +279,8 @@ public class HorizontalCandidateController: CandidateController {
|
|||
nextPageButton.setButtonType(.momentaryLight)
|
||||
nextPageButton.bezelStyle = .disclosure
|
||||
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)
|
||||
NSColor.controlBackgroundColor.setFill()
|
||||
NSBezierPath.fill(prevPageButton.bounds)
|
||||
|
@ -248,7 +291,8 @@ public class HorizontalCandidateController: CandidateController {
|
|||
prevPageButton.setButtonType(.momentaryLight)
|
||||
prevPageButton.bezelStyle = .disclosure
|
||||
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(prevPageButton)
|
||||
|
||||
|
@ -275,8 +319,8 @@ public class HorizontalCandidateController: CandidateController {
|
|||
}
|
||||
|
||||
public override func showNextPage() -> Bool {
|
||||
guard delegate != nil else {return false}
|
||||
if pageCount == 1 {return highlightNextCandidate()}
|
||||
guard delegate != nil else { return false }
|
||||
if pageCount == 1 { return highlightNextCandidate() }
|
||||
currentPage = (currentPage + 1 >= pageCount) ? 0 : currentPage + 1
|
||||
candidateView.highlightedIndex = 0
|
||||
layoutCandidateView()
|
||||
|
@ -284,8 +328,8 @@ public class HorizontalCandidateController: CandidateController {
|
|||
}
|
||||
|
||||
public override func showPreviousPage() -> Bool {
|
||||
guard delegate != nil else {return false}
|
||||
if pageCount == 1 {return highlightPreviousCandidate()}
|
||||
guard delegate != nil else { return false }
|
||||
if pageCount == 1 { return highlightPreviousCandidate() }
|
||||
currentPage = (currentPage == 0) ? pageCount - 1 : currentPage - 1
|
||||
candidateView.highlightedIndex = 0
|
||||
layoutCandidateView()
|
||||
|
@ -293,14 +337,18 @@ public class HorizontalCandidateController: CandidateController {
|
|||
}
|
||||
|
||||
public override func highlightNextCandidate() -> Bool {
|
||||
guard let delegate = delegate else {return false}
|
||||
selectedCandidateIndex = (selectedCandidateIndex + 1 >= delegate.candidateCountForController(self)) ? 0 : selectedCandidateIndex + 1
|
||||
guard let delegate = delegate else { return false }
|
||||
selectedCandidateIndex =
|
||||
(selectedCandidateIndex + 1 >= delegate.candidateCountForController(self))
|
||||
? 0 : selectedCandidateIndex + 1
|
||||
return true
|
||||
}
|
||||
|
||||
public override func highlightPreviousCandidate() -> Bool {
|
||||
guard let delegate = delegate else {return false}
|
||||
selectedCandidateIndex = (selectedCandidateIndex == 0) ? delegate.candidateCountForController(self) - 1 : selectedCandidateIndex - 1
|
||||
guard let delegate = delegate else { return false }
|
||||
selectedCandidateIndex =
|
||||
(selectedCandidateIndex == 0)
|
||||
? delegate.candidateCountForController(self) - 1 : selectedCandidateIndex - 1
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -357,7 +405,8 @@ extension HorizontalCandidateController {
|
|||
let candidate = delegate.candidateController(self, candidateAtIndex: index)
|
||||
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 frameRect = candidateView.frame
|
||||
frameRect.size = newSize
|
||||
|
@ -365,7 +414,7 @@ extension HorizontalCandidateController {
|
|||
|
||||
if pageCount > 1 && mgrPrefs.showPageButtonsInCandidateWindow {
|
||||
var buttonRect = nextPageButton.frame
|
||||
let spacing:CGFloat = 0.0
|
||||
let spacing: CGFloat = 0.0
|
||||
|
||||
buttonRect.size.height = floor(newSize.height / 2)
|
||||
|
||||
|
@ -373,7 +422,8 @@ extension HorizontalCandidateController {
|
|||
buttonRect.origin = NSPoint(x: newSize.width, y: buttonOriginY)
|
||||
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
|
||||
|
||||
newSize.width += 20
|
||||
|
@ -386,7 +436,8 @@ extension HorizontalCandidateController {
|
|||
|
||||
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.origin = NSMakePoint(topLeftPoint.x, topLeftPoint.y - frameRect.size.height)
|
||||
self.window?.setFrame(frameRect, display: false)
|
||||
|
|
|
@ -1,25 +1,32 @@
|
|||
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License).
|
||||
// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
// All possible vChewing-specific modifications are of:
|
||||
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
|
||||
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
1. The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor,
|
||||
except as required to fulfill notice requirements above.
|
||||
2. No trademark license is granted to use the trade names, trademarks, service
|
||||
marks, or product names of Contributor, except as required to fulfill notice
|
||||
requirements above.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
|
||||
fileprivate class VerticalCandidateView: NSView {
|
||||
private class VerticalCandidateView: NSView {
|
||||
var highlightedIndex: UInt = 0
|
||||
var action: Selector?
|
||||
weak var target: AnyObject?
|
||||
|
@ -58,17 +65,19 @@ fileprivate class VerticalCandidateView: NSView {
|
|||
let count = min(labels.count, candidates.count)
|
||||
keyLabels = Array(labels[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 calculatedWindowWidth = CGFloat()
|
||||
var newHeights = [CGFloat]()
|
||||
let baseSize = NSSize(width: 10240.0, height: 10240.0)
|
||||
for index in 0..<count {
|
||||
let rctCandidate = (dispCandidatesWithLabels[index] as NSString).boundingRect(with: baseSize, options: .usesLineFragmentOrigin, attributes: candidateWithLabelAttrDict)
|
||||
let rctCandidate = (dispCandidatesWithLabels[index] as NSString).boundingRect(
|
||||
with: baseSize, options: .usesLineFragmentOrigin,
|
||||
attributes: candidateWithLabelAttrDict)
|
||||
let cellWidth = rctCandidate.size.width + cellPadding
|
||||
let cellHeight = rctCandidate.size.height + cellPadding
|
||||
if (calculatedWindowWidth < rctCandidate.size.width) {
|
||||
if calculatedWindowWidth < rctCandidate.size.width {
|
||||
calculatedWindowWidth = rctCandidate.size.width + cellPadding
|
||||
}
|
||||
newWidths.append(cellWidth)
|
||||
|
@ -76,7 +85,7 @@ fileprivate class VerticalCandidateView: NSView {
|
|||
}
|
||||
elementWidths = newWidths
|
||||
elementHeights = newHeights
|
||||
windowWidth = calculatedWindowWidth + cellPadding;
|
||||
windowWidth = calculatedWindowWidth + cellPadding
|
||||
}
|
||||
|
||||
@objc(setKeyLabelFont:candidateFont:)
|
||||
|
@ -85,17 +94,24 @@ fileprivate class VerticalCandidateView: NSView {
|
|||
paraStyle.setParagraphStyle(NSParagraphStyle.default)
|
||||
paraStyle.alignment = .left
|
||||
|
||||
candidateWithLabelAttrDict = [.font: candidateFont,
|
||||
candidateWithLabelAttrDict = [
|
||||
.font: candidateFont,
|
||||
.paragraphStyle: paraStyle,
|
||||
.foregroundColor: NSColor.labelColor] // We still need this dummy section to make sure the space occupations of the candidates are correct.
|
||||
.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,
|
||||
.verticalGlyphForm: true as AnyObject,
|
||||
.foregroundColor: NSColor.secondaryLabelColor] // Candidate phrase text color
|
||||
candidateAttrDict = [.font: candidateFont,
|
||||
.foregroundColor: NSColor.secondaryLabelColor,
|
||||
] // Candidate phrase text color
|
||||
candidateAttrDict = [
|
||||
.font: candidateFont,
|
||||
.paragraphStyle: paraStyle,
|
||||
.foregroundColor: NSColor.labelColor] // Candidate index text color
|
||||
.foregroundColor: NSColor.labelColor,
|
||||
] // Candidate index text color
|
||||
let labelFontSize = labelFont.pointSize
|
||||
let candidateFontSize = candidateFont.pointSize
|
||||
let biggestSize = max(labelFontSize, candidateFontSize)
|
||||
|
@ -112,14 +128,22 @@ fileprivate class VerticalCandidateView: NSView {
|
|||
|
||||
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
|
||||
for index in 0..<elementHeights.count {
|
||||
let currentHeight = elementHeights[index]
|
||||
let rctCandidateArea = NSRect(x: 0.0, y: accuHeight, width: windowWidth, height: candidateTextHeight + cellPadding)
|
||||
let rctLabel = NSRect(x: cellPadding / 2 - 1, y: accuHeight + cellPadding / 2, width: keyLabelWidth, height: keyLabelHeight * 2.0)
|
||||
let rctCandidatePhrase = NSRect(x: cellPadding / 2 - 1 + keyLabelWidth, y: accuHeight + cellPadding / 2 - 1, width: windowWidth - keyLabelWidth, height: candidateTextHeight)
|
||||
let rctCandidateArea = NSRect(
|
||||
x: 0.0, y: accuHeight, width: windowWidth, height: candidateTextHeight + cellPadding
|
||||
)
|
||||
let rctLabel = NSRect(
|
||||
x: cellPadding / 2 - 1, y: accuHeight + cellPadding / 2, width: keyLabelWidth,
|
||||
height: keyLabelHeight * 2.0)
|
||||
let rctCandidatePhrase = NSRect(
|
||||
x: cellPadding / 2 - 1 + keyLabelWidth, y: accuHeight + cellPadding / 2 - 1,
|
||||
width: windowWidth - keyLabelWidth, height: candidateTextHeight)
|
||||
|
||||
var activeCandidateIndexAttr = keyLabelAttrDict
|
||||
var activeCandidateAttr = candidateAttrDict
|
||||
|
@ -128,28 +152,43 @@ fileprivate class VerticalCandidateView: NSView {
|
|||
// The background color of the highlightened candidate
|
||||
switch ctlInputMethod.currentKeyHandler.inputMode {
|
||||
case InputMode.imeModeCHS:
|
||||
NSColor.systemRed.blended(withFraction: colorBlendAmount, of: NSColor.controlBackgroundColor)!.setFill()
|
||||
NSColor.systemRed.blended(
|
||||
withFraction: colorBlendAmount,
|
||||
of: NSColor.controlBackgroundColor)!
|
||||
.setFill()
|
||||
case InputMode.imeModeCHT:
|
||||
NSColor.systemBlue.blended(withFraction: colorBlendAmount, of: NSColor.controlBackgroundColor)!.setFill()
|
||||
NSColor.systemBlue.blended(
|
||||
withFraction: colorBlendAmount,
|
||||
of: NSColor.controlBackgroundColor)!
|
||||
.setFill()
|
||||
default:
|
||||
NSColor.alternateSelectedControlColor.setFill()
|
||||
}
|
||||
activeCandidateIndexAttr[.foregroundColor] = NSColor.selectedMenuItemTextColor.withAlphaComponent(0.84) // The index text color of the highlightened candidate
|
||||
activeCandidateAttr[.foregroundColor] = NSColor.selectedMenuItemTextColor // The phrase text color of the highlightened candidate
|
||||
// Highlightened index text color
|
||||
activeCandidateIndexAttr[.foregroundColor] = NSColor.selectedMenuItemTextColor
|
||||
.withAlphaComponent(0.84)
|
||||
// Highlightened phrase text color
|
||||
activeCandidateAttr[.foregroundColor] = NSColor.selectedMenuItemTextColor
|
||||
} else {
|
||||
NSColor.controlBackgroundColor.setFill()
|
||||
}
|
||||
switch ctlInputMethod.currentKeyHandler.inputMode {
|
||||
case InputMode.imeModeCHS:
|
||||
if #available(macOS 12.0, *) {activeCandidateAttr[.languageIdentifier] = "zh-Hans" as AnyObject}
|
||||
if #available(macOS 12.0, *) {
|
||||
activeCandidateAttr[.languageIdentifier] = "zh-Hans" as AnyObject
|
||||
}
|
||||
case InputMode.imeModeCHT:
|
||||
if #available(macOS 12.0, *) {activeCandidateAttr[.languageIdentifier] = "zh-Hant" as AnyObject}
|
||||
if #available(macOS 12.0, *) {
|
||||
activeCandidateAttr[.languageIdentifier] = "zh-Hant" as AnyObject
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
NSBezierPath.fill(rctCandidateArea)
|
||||
(keyLabels[index] as NSString).draw(in: rctLabel, withAttributes: activeCandidateIndexAttr)
|
||||
(displayedCandidates[index] as NSString).draw(in: rctCandidatePhrase, withAttributes: activeCandidateAttr)
|
||||
(keyLabels[index] as NSString).draw(
|
||||
in: rctLabel, withAttributes: activeCandidateIndexAttr)
|
||||
(displayedCandidates[index] as NSString).draw(
|
||||
in: rctCandidatePhrase, withAttributes: activeCandidateAttr)
|
||||
accuHeight += currentHeight
|
||||
}
|
||||
}
|
||||
|
@ -212,7 +251,8 @@ public class VerticalCandidateController: CandidateController {
|
|||
public init() {
|
||||
var contentRect = NSRect(x: 128.0, y: 128.0, width: 0.0, height: 0.0)
|
||||
let styleMask: NSWindow.StyleMask = [.nonactivatingPanel]
|
||||
let panel = NSPanel(contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false)
|
||||
let panel = NSPanel(
|
||||
contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false)
|
||||
panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel) + 1)
|
||||
panel.hasShadow = true
|
||||
panel.isOpaque = false
|
||||
|
@ -222,7 +262,8 @@ public class VerticalCandidateController: CandidateController {
|
|||
candidateView = VerticalCandidateView(frame: contentRect)
|
||||
|
||||
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
|
||||
if #available(macOS 10.13, *) {
|
||||
candidateView.layer?.cornerRadius = 6.0
|
||||
|
@ -231,7 +272,7 @@ public class VerticalCandidateController: CandidateController {
|
|||
panel.contentView?.addSubview(candidateView)
|
||||
|
||||
contentRect.size = NSSize(width: 20.0, height: 10.0) // Reduce the button width
|
||||
let buttonAttribute: [NSAttributedString.Key : Any] = [.font : NSFont.systemFont(ofSize: 9.0)]
|
||||
let buttonAttribute: [NSAttributedString.Key: Any] = [.font: NSFont.systemFont(ofSize: 9.0)]
|
||||
|
||||
nextPageButton = NSButton(frame: contentRect)
|
||||
NSColor.controlBackgroundColor.setFill()
|
||||
|
@ -243,7 +284,8 @@ public class VerticalCandidateController: CandidateController {
|
|||
nextPageButton.setButtonType(.momentaryLight)
|
||||
nextPageButton.bezelStyle = .disclosure
|
||||
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)
|
||||
NSColor.controlBackgroundColor.setFill()
|
||||
NSBezierPath.fill(prevPageButton.bounds)
|
||||
|
@ -254,7 +296,8 @@ public class VerticalCandidateController: CandidateController {
|
|||
prevPageButton.setButtonType(.momentaryLight)
|
||||
prevPageButton.bezelStyle = .disclosure
|
||||
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(prevPageButton)
|
||||
|
||||
|
@ -281,8 +324,8 @@ public class VerticalCandidateController: CandidateController {
|
|||
}
|
||||
|
||||
public override func showNextPage() -> Bool {
|
||||
guard delegate != nil else {return false}
|
||||
if pageCount == 1 {return highlightNextCandidate()}
|
||||
guard delegate != nil else { return false }
|
||||
if pageCount == 1 { return highlightNextCandidate() }
|
||||
currentPage = (currentPage + 1 >= pageCount) ? 0 : currentPage + 1
|
||||
candidateView.highlightedIndex = 0
|
||||
layoutCandidateView()
|
||||
|
@ -290,8 +333,8 @@ public class VerticalCandidateController: CandidateController {
|
|||
}
|
||||
|
||||
public override func showPreviousPage() -> Bool {
|
||||
guard delegate != nil else {return false}
|
||||
if pageCount == 1 {return highlightPreviousCandidate()}
|
||||
guard delegate != nil else { return false }
|
||||
if pageCount == 1 { return highlightPreviousCandidate() }
|
||||
currentPage = (currentPage == 0) ? pageCount - 1 : currentPage - 1
|
||||
candidateView.highlightedIndex = 0
|
||||
layoutCandidateView()
|
||||
|
@ -299,14 +342,18 @@ public class VerticalCandidateController: CandidateController {
|
|||
}
|
||||
|
||||
public override func highlightNextCandidate() -> Bool {
|
||||
guard let delegate = delegate else {return false}
|
||||
selectedCandidateIndex = (selectedCandidateIndex + 1 >= delegate.candidateCountForController(self)) ? 0 : selectedCandidateIndex + 1
|
||||
guard let delegate = delegate else { return false }
|
||||
selectedCandidateIndex =
|
||||
(selectedCandidateIndex + 1 >= delegate.candidateCountForController(self))
|
||||
? 0 : selectedCandidateIndex + 1
|
||||
return true
|
||||
}
|
||||
|
||||
public override func highlightPreviousCandidate() -> Bool {
|
||||
guard let delegate = delegate else {return false}
|
||||
selectedCandidateIndex = (selectedCandidateIndex == 0) ? delegate.candidateCountForController(self) - 1 : selectedCandidateIndex - 1
|
||||
guard let delegate = delegate else { return false }
|
||||
selectedCandidateIndex =
|
||||
(selectedCandidateIndex == 0)
|
||||
? delegate.candidateCountForController(self) - 1 : selectedCandidateIndex - 1
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -363,7 +410,8 @@ extension VerticalCandidateController {
|
|||
let candidate = delegate.candidateController(self, candidateAtIndex: index)
|
||||
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 frameRect = candidateView.frame
|
||||
frameRect.size = newSize
|
||||
|
@ -371,7 +419,7 @@ extension VerticalCandidateController {
|
|||
|
||||
if pageCount > 1 && mgrPrefs.showPageButtonsInCandidateWindow {
|
||||
var buttonRect = nextPageButton.frame
|
||||
let spacing:CGFloat = 0.0
|
||||
let spacing: CGFloat = 0.0
|
||||
|
||||
// buttonRect.size.height = floor(candidateTextHeight + cellPadding / 2)
|
||||
|
||||
|
@ -379,7 +427,8 @@ extension VerticalCandidateController {
|
|||
buttonRect.origin = NSPoint(x: newSize.width, y: buttonOriginY)
|
||||
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
|
||||
|
||||
newSize.width += 20
|
||||
|
@ -392,7 +441,8 @@ extension VerticalCandidateController {
|
|||
|
||||
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.origin = NSMakePoint(topLeftPoint.x, topLeftPoint.y - frameRect.size.height)
|
||||
self.window?.setFrame(frameRect, display: false)
|
||||
|
|
|
@ -1,20 +1,26 @@
|
|||
// Copyright (c) 2021 and onwards Weizhong Yang (MIT License).
|
||||
// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
|
||||
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
1. The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor,
|
||||
except as required to fulfill notice requirements above.
|
||||
2. No trademark license is granted to use the trade names, trademarks, service
|
||||
marks, or product names of Contributor, except as required to fulfill notice
|
||||
requirements above.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
|
@ -45,12 +51,13 @@ public class NotifierController: NSWindowController, NotifierWindowDelegate {
|
|||
let attr: [NSAttributedString.Key: AnyObject] = [
|
||||
.foregroundColor: foregroundColor,
|
||||
.font: NSFont.boldSystemFont(ofSize: NSFont.systemFontSize(for: .regular)),
|
||||
.paragraphStyle: paraStyle
|
||||
.paragraphStyle: paraStyle,
|
||||
]
|
||||
let attrString = NSAttributedString(string: message, attributes: attr)
|
||||
messageTextField.attributedStringValue = attrString
|
||||
let width = window?.frame.width ?? kWindowWidth
|
||||
let rect = attrString.boundingRect(with: NSSize(width: width, height: 1600), options: .usesLineFragmentOrigin)
|
||||
let rect = attrString.boundingRect(
|
||||
with: NSSize(width: width, height: 1600), options: .usesLineFragmentOrigin)
|
||||
let height = rect.height
|
||||
let x = messageTextField.frame.origin.x
|
||||
let y = ((window?.frame.height ?? kWindowHeight) - height) / 2
|
||||
|
@ -105,7 +112,8 @@ public class NotifierController: NSWindowController, NotifierWindowDelegate {
|
|||
transparentVisualEffect.blendingMode = .behindWindow
|
||||
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.isMovableByWindowBackground = true
|
||||
panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel))
|
||||
|
@ -170,7 +178,10 @@ public class NotifierController: NSWindowController, NotifierWindowDelegate {
|
|||
setStartLocation()
|
||||
moveIn()
|
||||
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) {
|
||||
|
@ -186,7 +197,9 @@ public class NotifierController: NSWindowController, NotifierWindowDelegate {
|
|||
waitTimer?.invalidate()
|
||||
waitTimer = nil
|
||||
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() {
|
||||
|
|
|
@ -1,20 +1,26 @@
|
|||
// Copyright (c) 2021 and onwards Weizhong Yang (MIT License).
|
||||
// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
|
||||
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
1. The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor,
|
||||
except as required to fulfill notice requirements above.
|
||||
2. No trademark license is granted to use the trade names, trademarks, service
|
||||
marks, or product names of Contributor, except as required to fulfill notice
|
||||
requirements above.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
|
@ -33,7 +39,8 @@ public class TooltipController: NSWindowController {
|
|||
public init() {
|
||||
let contentRect = NSRect(x: 128.0, y: 128.0, width: 300.0, height: 20.0)
|
||||
let styleMask: NSWindow.StyleMask = [.borderless, .nonactivatingPanel]
|
||||
let panel = NSPanel(contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false)
|
||||
let panel = NSPanel(
|
||||
contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false)
|
||||
panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel) + 1)
|
||||
panel.hasShadow = true
|
||||
|
||||
|
@ -76,10 +83,9 @@ public class TooltipController: NSWindowController {
|
|||
var screenFrame = NSScreen.main?.visibleFrame ?? NSRect.zero
|
||||
for screen in NSScreen.screens {
|
||||
let frame = screen.visibleFrame
|
||||
if windowTopLeftPoint.x >= frame.minX &&
|
||||
windowTopLeftPoint.x <= frame.maxX &&
|
||||
windowTopLeftPoint.y >= frame.minY &&
|
||||
windowTopLeftPoint.y <= frame.maxY {
|
||||
if windowTopLeftPoint.x >= frame.minX && windowTopLeftPoint.x <= frame.maxX
|
||||
&& windowTopLeftPoint.y >= frame.minY && windowTopLeftPoint.y <= frame.maxY
|
||||
{
|
||||
screenFrame = frame
|
||||
break
|
||||
}
|
||||
|
@ -112,8 +118,9 @@ public class TooltipController: NSWindowController {
|
|||
}
|
||||
|
||||
private func adjustSize() {
|
||||
let attrString = messageTextField.attributedStringValue;
|
||||
var rect = attrString.boundingRect(with: NSSize(width: 1600.0, height: 1600.0), options: .usesLineFragmentOrigin)
|
||||
let attrString = messageTextField.attributedStringValue
|
||||
var rect = attrString.boundingRect(
|
||||
with: NSSize(width: 1600.0, height: 1600.0), options: .usesLineFragmentOrigin)
|
||||
rect.size.width += 10
|
||||
messageTextField.frame = rect
|
||||
window?.setFrame(rect, display: true)
|
||||
|
|
|
@ -1,20 +1,27 @@
|
|||
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License).
|
||||
// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
// All possible vChewing-specific modifications are of:
|
||||
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
|
||||
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
1. The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor,
|
||||
except as required to fulfill notice requirements above.
|
||||
2. No trademark license is granted to use the trade names, trademarks, service
|
||||
marks, or product names of Contributor, except as required to fulfill notice
|
||||
requirements above.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
|
@ -30,17 +37,23 @@ import Cocoa
|
|||
window?.standardWindowButton(.closeButton)?.isHidden = true
|
||||
window?.standardWindowButton(.miniaturizeButton)?.isHidden = true
|
||||
window?.standardWindowButton(.zoomButton)?.isHidden = true
|
||||
guard let installingVersion = Bundle.main.infoDictionary?[kCFBundleVersionKey as String] as? String,
|
||||
let versionString = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String else {
|
||||
guard
|
||||
let installingVersion = Bundle.main.infoDictionary?[kCFBundleVersionKey as String]
|
||||
as? String,
|
||||
let versionString = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
|
||||
else {
|
||||
return
|
||||
}
|
||||
if let copyrightLabel = Bundle.main.localizedInfoDictionary?["NSHumanReadableCopyright"] as? String {
|
||||
if let copyrightLabel = Bundle.main.localizedInfoDictionary?["NSHumanReadableCopyright"]
|
||||
as? String
|
||||
{
|
||||
appCopyrightLabel.stringValue = copyrightLabel
|
||||
}
|
||||
if let eulaContent = Bundle.main.localizedInfoDictionary?["CFEULAContent"] as? String {
|
||||
appEULAContent.string = eulaContent
|
||||
}
|
||||
appVersionLabel.stringValue = String(format: "%@ Build %@", versionString, installingVersion)
|
||||
appVersionLabel.stringValue = String(
|
||||
format: "%@ Build %@", versionString, installingVersion)
|
||||
}
|
||||
|
||||
@IBAction func btnWiki(_ sender: NSButton) {
|
||||
|
|
|
@ -1,20 +1,27 @@
|
|||
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License).
|
||||
// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
// All possible vChewing-specific modifications are of:
|
||||
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
|
||||
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
1. The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor,
|
||||
except as required to fulfill notice requirements above.
|
||||
2. No trademark license is granted to use the trade names, trademarks, service
|
||||
marks, or product names of Contributor, except as required to fulfill notice
|
||||
requirements above.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
|
@ -34,7 +41,10 @@ class ctlNonModalAlertWindow: NSWindowController {
|
|||
@IBOutlet weak var cancelButton: NSButton!
|
||||
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 {
|
||||
self.delegate?.ctlNonModalAlertWindowDidCancel(self)
|
||||
}
|
||||
|
@ -84,7 +94,9 @@ class ctlNonModalAlertWindow: NSWindowController {
|
|||
var infiniteHeightFrame = oldFrame
|
||||
infiniteHeightFrame.size.width -= 4.0
|
||||
infiniteHeightFrame.size.height = 10240
|
||||
newFrame = (content as NSString).boundingRect(with: infiniteHeightFrame.size, options: [.usesLineFragmentOrigin], attributes: [.font: contentTextField.font!])
|
||||
newFrame = (content as NSString).boundingRect(
|
||||
with: infiniteHeightFrame.size, options: [.usesLineFragmentOrigin],
|
||||
attributes: [.font: contentTextField.font!])
|
||||
newFrame.size.width = max(newFrame.size.width, oldFrame.size.width)
|
||||
newFrame.size.height += 4.0
|
||||
newFrame.origin = oldFrame.origin
|
||||
|
|
|
@ -1,30 +1,37 @@
|
|||
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License).
|
||||
// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
// All possible vChewing-specific modifications are of:
|
||||
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
|
||||
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
1. The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor,
|
||||
except as required to fulfill notice requirements above.
|
||||
2. No trademark license is granted to use the trade names, trademarks, service
|
||||
marks, or product names of Contributor, except as required to fulfill notice
|
||||
requirements above.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
import Carbon
|
||||
import Cocoa
|
||||
|
||||
// Extend the RangeReplaceableCollection to allow it clean duplicated characters.
|
||||
extension RangeReplaceableCollection where Element: Hashable {
|
||||
var charDeDuplicate: Self {
|
||||
var set = Set<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() {
|
||||
super.windowDidLoad()
|
||||
|
||||
lblCurrentlySpecifiedUserDataFolder.placeholderString = mgrLangModel.dataFolderPath(isDefaultFolder: true)
|
||||
lblCurrentlySpecifiedUserDataFolder.placeholderString = mgrLangModel.dataFolderPath(
|
||||
isDefaultFolder: true)
|
||||
|
||||
let languages = ["auto", "en", "zh-Hans", "zh-Hant", "ja"]
|
||||
var autoMUISelectItem: NSMenuItem? = nil
|
||||
|
@ -79,15 +87,18 @@ extension RangeReplaceableCollection where Element: Hashable {
|
|||
|
||||
basisKeyboardLayoutButton.menu?.removeAllItems()
|
||||
|
||||
let menuItem_AppleZhuyinBopomofo = NSMenuItem()
|
||||
menuItem_AppleZhuyinBopomofo.title = String(format: NSLocalizedString("Apple Zhuyin Bopomofo", comment: ""))
|
||||
menuItem_AppleZhuyinBopomofo.representedObject = String("com.apple.keylayout.ZhuyinBopomofo")
|
||||
basisKeyboardLayoutButton.menu?.addItem(menuItem_AppleZhuyinBopomofo)
|
||||
let itmAppleZhuyinBopomofo = NSMenuItem()
|
||||
itmAppleZhuyinBopomofo.title = String(
|
||||
format: NSLocalizedString("Apple Zhuyin Bopomofo", comment: ""))
|
||||
itmAppleZhuyinBopomofo.representedObject = String(
|
||||
"com.apple.keylayout.ZhuyinBopomofo")
|
||||
basisKeyboardLayoutButton.menu?.addItem(itmAppleZhuyinBopomofo)
|
||||
|
||||
let menuItem_AppleZhuyinEten = NSMenuItem()
|
||||
menuItem_AppleZhuyinEten.title = String(format: NSLocalizedString("Apple Zhuyin Eten", comment: ""))
|
||||
menuItem_AppleZhuyinEten.representedObject = String("com.apple.keylayout.ZhuyinEten")
|
||||
basisKeyboardLayoutButton.menu?.addItem(menuItem_AppleZhuyinEten)
|
||||
let itmAppleZhuyinEten = NSMenuItem()
|
||||
itmAppleZhuyinEten.title = String(
|
||||
format: NSLocalizedString("Apple Zhuyin Eten", comment: ""))
|
||||
itmAppleZhuyinEten.representedObject = String("com.apple.keylayout.ZhuyinEten")
|
||||
basisKeyboardLayoutButton.menu?.addItem(itmAppleZhuyinEten)
|
||||
|
||||
let basisKeyboardLayoutID = mgrPrefs.basisKeyboardLayout
|
||||
|
||||
|
@ -101,8 +112,11 @@ extension RangeReplaceableCollection where Element: Hashable {
|
|||
continue
|
||||
}
|
||||
|
||||
if let asciiCapablePtr = TISGetInputSourceProperty(source, kTISPropertyInputSourceIsASCIICapable) {
|
||||
let asciiCapable = Unmanaged<CFBoolean>.fromOpaque(asciiCapablePtr).takeUnretainedValue()
|
||||
if let asciiCapablePtr = TISGetInputSourceProperty(
|
||||
source, kTISPropertyInputSourceIsASCIICapable)
|
||||
{
|
||||
let asciiCapable = Unmanaged<CFBoolean>.fromOpaque(asciiCapablePtr)
|
||||
.takeUnretainedValue()
|
||||
if asciiCapable != kCFBooleanTrue {
|
||||
continue
|
||||
}
|
||||
|
@ -120,12 +134,14 @@ extension RangeReplaceableCollection where Element: Hashable {
|
|||
}
|
||||
|
||||
guard let sourceIDPtr = TISGetInputSourceProperty(source, kTISPropertyInputSourceID),
|
||||
let localizedNamePtr = TISGetInputSourceProperty(source, kTISPropertyLocalizedName) else {
|
||||
let localizedNamePtr = TISGetInputSourceProperty(source, kTISPropertyLocalizedName)
|
||||
else {
|
||||
continue
|
||||
}
|
||||
|
||||
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()
|
||||
menuItem.title = localizedName
|
||||
|
@ -142,9 +158,9 @@ extension RangeReplaceableCollection where Element: Hashable {
|
|||
|
||||
switch basisKeyboardLayoutID {
|
||||
case "com.apple.keylayout.ZhuyinBopomofo":
|
||||
chosenBaseKeyboardLayoutItem = menuItem_AppleZhuyinBopomofo
|
||||
chosenBaseKeyboardLayoutItem = itmAppleZhuyinBopomofo
|
||||
case "com.apple.keylayout.ZhuyinEten":
|
||||
chosenBaseKeyboardLayoutItem = menuItem_AppleZhuyinEten
|
||||
chosenBaseKeyboardLayoutItem = itmAppleZhuyinEten
|
||||
default:
|
||||
break // nothing to do
|
||||
}
|
||||
|
@ -198,10 +214,9 @@ extension RangeReplaceableCollection where Element: Hashable {
|
|||
}
|
||||
}
|
||||
if let language = uiLanguageButton.selectedItem?.representedObject as? String {
|
||||
if (language != "auto") {
|
||||
if language != "auto" {
|
||||
mgrPrefs.appleLanguages = [language]
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
UserDefaults.standard.removeObject(forKey: "AppleLanguages")
|
||||
}
|
||||
|
||||
|
@ -215,18 +230,21 @@ extension RangeReplaceableCollection where Element: Hashable {
|
|||
}
|
||||
|
||||
@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
|
||||
}
|
||||
do {
|
||||
try mgrPrefs.validate(candidateKeys: keys)
|
||||
mgrPrefs.candidateKeys = keys
|
||||
selectionKeyComboBox.stringValue = mgrPrefs.candidateKeys
|
||||
}
|
||||
catch mgrPrefs.CandidateKeyError.empty {
|
||||
} catch mgrPrefs.CandidateKeyError.empty {
|
||||
selectionKeyComboBox.stringValue = mgrPrefs.candidateKeys
|
||||
}
|
||||
catch {
|
||||
} catch {
|
||||
if let window = window {
|
||||
let alert = NSAlert(error: error)
|
||||
alert.beginSheetModal(for: window) { response in
|
||||
|
@ -243,31 +261,35 @@ extension RangeReplaceableCollection where Element: Hashable {
|
|||
}
|
||||
|
||||
@IBAction func chooseUserDataFolderToSpecify(_ sender: Any) {
|
||||
IME.dlgOpenPath.title = NSLocalizedString("Choose your desired user data folder.", comment: "");
|
||||
IME.dlgOpenPath.showsResizeIndicator = true;
|
||||
IME.dlgOpenPath.showsHiddenFiles = true;
|
||||
IME.dlgOpenPath.canChooseFiles = false;
|
||||
IME.dlgOpenPath.canChooseDirectories = true;
|
||||
IME.dlgOpenPath.title = NSLocalizedString(
|
||||
"Choose your desired user data folder.", comment: "")
|
||||
IME.dlgOpenPath.showsResizeIndicator = true
|
||||
IME.dlgOpenPath.showsHiddenFiles = true
|
||||
IME.dlgOpenPath.canChooseFiles = false
|
||||
IME.dlgOpenPath.canChooseDirectories = true
|
||||
|
||||
let PreviousFolderValidity = mgrLangModel.checkIfSpecifiedUserDataFolderValid(NSString(string: mgrPrefs.userDataFolderSpecified).expandingTildeInPath)
|
||||
let bolPreviousFolderValidity = mgrLangModel.checkIfSpecifiedUserDataFolderValid(
|
||||
NSString(string: mgrPrefs.userDataFolderSpecified).expandingTildeInPath)
|
||||
|
||||
if self.window != nil {
|
||||
IME.dlgOpenPath.beginSheetModal(for: self.window!) { result in
|
||||
if result == NSApplication.ModalResponse.OK {
|
||||
if (IME.dlgOpenPath.url != nil) {
|
||||
if (mgrLangModel.checkIfSpecifiedUserDataFolderValid(IME.dlgOpenPath.url!.path)) {
|
||||
if IME.dlgOpenPath.url != nil {
|
||||
if mgrLangModel.checkIfSpecifiedUserDataFolderValid(
|
||||
IME.dlgOpenPath.url!.path)
|
||||
{
|
||||
mgrPrefs.userDataFolderSpecified = IME.dlgOpenPath.url!.path
|
||||
IME.initLangModels(userOnly: true)
|
||||
} else {
|
||||
clsSFX.beep()
|
||||
if !PreviousFolderValidity {
|
||||
if !bolPreviousFolderValidity {
|
||||
self.resetSpecifiedUserDataFolder(self)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if !PreviousFolderValidity {
|
||||
if !bolPreviousFolderValidity {
|
||||
self.resetSpecifiedUserDataFolder(self)
|
||||
}
|
||||
return
|
||||
|
|
|
@ -1,19 +1,25 @@
|
|||
// Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
|
||||
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
1. The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor,
|
||||
except as required to fulfill notice requirements above.
|
||||
2. No trademark license is granted to use the trade names, trademarks, service
|
||||
marks, or product names of Contributor, except as required to fulfill notice
|
||||
requirements above.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
|
@ -31,12 +37,12 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||
// Insert code here to tear down your application
|
||||
}
|
||||
|
||||
func applicationShouldTerminate(_ sender: NSApplication)-> NSApplication.TerminateReply {
|
||||
func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {
|
||||
return .terminateNow
|
||||
}
|
||||
// New About Window
|
||||
@objc func showAbout() {
|
||||
if (ctlAboutWindowInstance == nil) {
|
||||
if ctlAboutWindowInstance == nil {
|
||||
ctlAboutWindowInstance = ctlAboutWindow.init(windowNibName: "frmAboutWindow")
|
||||
}
|
||||
ctlAboutWindowInstance?.window?.center()
|
||||
|
|
|
@ -1,23 +1,29 @@
|
|||
// Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
|
||||
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
1. The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor,
|
||||
except as required to fulfill notice requirements above.
|
||||
2. No trademark license is granted to use the trade names, trademarks, service
|
||||
marks, or product names of Contributor, except as required to fulfill notice
|
||||
requirements above.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
import Cocoa
|
||||
import Foundation
|
||||
|
||||
class Content: NSObject {
|
||||
@objc dynamic var contentString = ""
|
||||
|
|
|
@ -1,19 +1,25 @@
|
|||
// Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
|
||||
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
1. The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor,
|
||||
except as required to fulfill notice requirements above.
|
||||
2. No trademark license is granted to use the trade names, trademarks, service
|
||||
marks, or product names of Contributor, except as required to fulfill notice
|
||||
requirements above.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
|
@ -36,7 +42,9 @@ class Document: NSDocument {
|
|||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
|
@ -53,7 +61,9 @@ class Document: NSDocument {
|
|||
let storyboard = NSStoryboard(name: NSStoryboard.Name("Main"), bundle: nil)
|
||||
if let windowController =
|
||||
storyboard.instantiateController(
|
||||
withIdentifier: NSStoryboard.SceneIdentifier("Document Window Controller")) as? NSWindowController {
|
||||
withIdentifier: NSStoryboard.SceneIdentifier("Document Window Controller"))
|
||||
as? NSWindowController
|
||||
{
|
||||
addWindowController(windowController)
|
||||
|
||||
// Set the view controller's represented object as your document.
|
||||
|
@ -69,7 +79,7 @@ class Document: NSDocument {
|
|||
/// - Tag: readExample
|
||||
override func read(from data: Data, ofType typeName: String) throws {
|
||||
var strToDealWith = String(decoding: data, as: UTF8.self)
|
||||
strToDealWith.formatConsolidate(HYPY2BPMF: false)
|
||||
strToDealWith.formatConsolidate(cnvHYPYtoBPMF: false)
|
||||
let processedIncomingData = Data(strToDealWith.utf8)
|
||||
content.read(from: processedIncomingData)
|
||||
}
|
||||
|
@ -77,7 +87,7 @@ class Document: NSDocument {
|
|||
/// - Tag: writeExample
|
||||
override func data(ofType typeName: String) throws -> Data {
|
||||
var strToDealWith = content.contentString
|
||||
strToDealWith.formatConsolidate(HYPY2BPMF: true)
|
||||
strToDealWith.formatConsolidate(cnvHYPYtoBPMF: true)
|
||||
let outputData = Data(strToDealWith.utf8)
|
||||
return outputData
|
||||
}
|
||||
|
@ -96,7 +106,8 @@ class Document: NSDocument {
|
|||
thePrintInfo.topMargin = 72.0
|
||||
thePrintInfo.bottomMargin = 72.0
|
||||
|
||||
printInfo.dictionary().setObject(NSNumber(value: true),
|
||||
printInfo.dictionary().setObject(
|
||||
NSNumber(value: true),
|
||||
forKey: NSPrintInfo.AttributeKey.headerAndFooter as NSCopying)
|
||||
|
||||
return thePrintInfo
|
||||
|
@ -104,7 +115,8 @@ class Document: NSDocument {
|
|||
|
||||
@objc
|
||||
func printOperationDidRun(
|
||||
_ printOperation: NSPrintOperation, success: Bool, contextInfo: UnsafeMutableRawPointer?) {
|
||||
_ printOperation: NSPrintOperation, success: Bool, contextInfo: UnsafeMutableRawPointer?
|
||||
) {
|
||||
// Printing finished...
|
||||
}
|
||||
|
||||
|
@ -112,8 +124,10 @@ class Document: NSDocument {
|
|||
// Print the NSTextView.
|
||||
|
||||
// Create a copy to manipulate for printing.
|
||||
let pageSize = NSSize(width: (printInfo.paperSize.width), height: (printInfo.paperSize.height))
|
||||
let textView = NSTextView(frame: NSRect(x: 0.0, y: 0.0, width: pageSize.width, height: pageSize.height))
|
||||
let pageSize = NSSize(
|
||||
width: (printInfo.paperSize.width), height: (printInfo.paperSize.height))
|
||||
let textView = NSTextView(
|
||||
frame: NSRect(x: 0.0, y: 0.0, width: pageSize.width, height: pageSize.height))
|
||||
|
||||
// Make sure we print on a white background.
|
||||
textView.appearance = NSAppearance(named: .aqua)
|
||||
|
|
|
@ -1,19 +1,25 @@
|
|||
// Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
|
||||
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
1. The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor,
|
||||
except as required to fulfill notice requirements above.
|
||||
2. No trademark license is granted to use the trade names, trademarks, service
|
||||
marks, or product names of Contributor, except as required to fulfill notice
|
||||
requirements above.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
|
@ -22,15 +28,17 @@ extension String {
|
|||
mutating func regReplace(pattern: String, replaceWith: String = "") {
|
||||
// Ref: https://stackoverflow.com/a/40993403/4162914 && https://stackoverflow.com/a/71291137/4162914
|
||||
do {
|
||||
let regex = try NSRegularExpression(pattern: pattern, options: [.caseInsensitive, .anchorsMatchLines])
|
||||
let regex = try NSRegularExpression(
|
||||
pattern: pattern, options: [.caseInsensitive, .anchorsMatchLines])
|
||||
let range = NSRange(self.startIndex..., in: self)
|
||||
self = regex.stringByReplacingMatches(in: self, options: [], range: range, withTemplate: replaceWith)
|
||||
self = regex.stringByReplacingMatches(
|
||||
in: self, options: [], range: range, withTemplate: replaceWith)
|
||||
} catch { return }
|
||||
}
|
||||
mutating func selfReplace(_ strOf: String, _ strWith: String = "") {
|
||||
self = self.replacingOccurrences(of: strOf, with: strWith)
|
||||
}
|
||||
mutating func formatConsolidate(HYPY2BPMF: Bool) {
|
||||
mutating func formatConsolidate(cnvHYPYtoBPMF: Bool) {
|
||||
// Step 1: Consolidating formats per line.
|
||||
var strProcessed = self
|
||||
// 預處理格式
|
||||
|
@ -41,7 +49,7 @@ extension String {
|
|||
// 統整連續空格為一個 ASCII 空格
|
||||
strProcessed.regReplace(pattern: #"( +| +| +|\t+)+"#, 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) == " " { // 去除檔案開頭空格
|
||||
strProcessed.removeFirst()
|
||||
}
|
||||
|
@ -49,7 +57,7 @@ extension String {
|
|||
strProcessed.removeLast()
|
||||
}
|
||||
var arrData = [""]
|
||||
if HYPY2BPMF {
|
||||
if cnvHYPYtoBPMF {
|
||||
// Step 0: Convert HanyuPinyin to Bopomofo.
|
||||
arrData = strProcessed.components(separatedBy: "\n")
|
||||
strProcessed = "" // Reset its value
|
||||
|
@ -492,8 +500,8 @@ extension String {
|
|||
}
|
||||
}
|
||||
|
||||
// Step 3: Add Formatted Pragma
|
||||
let hdrFormatted = "# 𝙵𝙾𝚁𝙼𝙰𝚃 𝚘𝚛𝚐.𝚊𝚝𝚎𝚕𝚒𝚎𝚛𝙸𝚗𝚖𝚞.𝚟𝚌𝚑𝚎𝚠𝚒𝚗𝚐.𝚞𝚜𝚎𝚛𝙻𝚊𝚗𝚐𝚞𝚊𝚐𝚎𝙼𝚘𝚍𝚎𝚕𝙳𝚊𝚝𝚊.𝚏𝚘𝚛𝚖𝚊𝚝𝚝𝚎𝚍\n" // Sorted Header
|
||||
// Step 3: Add Formatted Pragma, the Sorted Header:
|
||||
let hdrFormatted = "# 𝙵𝙾𝚁𝙼𝙰𝚃 𝚘𝚛𝚐.𝚊𝚝𝚎𝚕𝚒𝚎𝚛𝙸𝚗𝚖𝚞.𝚟𝚌𝚑𝚎𝚠𝚒𝚗𝚐.𝚞𝚜𝚎𝚛𝙻𝚊𝚗𝚐𝚞𝚊𝚐𝚎𝙼𝚘𝚍𝚎𝚕𝙳𝚊𝚝𝚊.𝚏𝚘𝚛𝚖𝚊𝚝𝚝𝚎𝚍\n"
|
||||
strProcessed = hdrFormatted + strProcessed // Add Sorted Header
|
||||
|
||||
// Step 4: Deduplication.
|
||||
|
|
|
@ -1,19 +1,25 @@
|
|||
// Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
|
||||
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
1. The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor,
|
||||
except as required to fulfill notice requirements above.
|
||||
2. No trademark license is granted to use the trade names, trademarks, service
|
||||
marks, or product names of Contributor, except as required to fulfill notice
|
||||
requirements above.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
|
|
|
@ -1,19 +1,25 @@
|
|||
// Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
|
||||
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
1. The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor,
|
||||
except as required to fulfill notice requirements above.
|
||||
2. No trademark license is granted to use the trade names, trademarks, service
|
||||
marks, or product names of Contributor, except as required to fulfill notice
|
||||
requirements above.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
|
|
|
@ -1,20 +1,27 @@
|
|||
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License).
|
||||
// All possible vChewing-specific modifications are (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
// All possible vChewing-specific modifications are of:
|
||||
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
|
||||
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
1. The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor,
|
||||
except as required to fulfill notice requirements above.
|
||||
2. No trademark license is granted to use the trade names, trademarks, service
|
||||
marks, or product names of Contributor, except as required to fulfill notice
|
||||
requirements above.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
|
@ -30,17 +37,23 @@ import Cocoa
|
|||
window?.standardWindowButton(.closeButton)?.isHidden = true
|
||||
window?.standardWindowButton(.miniaturizeButton)?.isHidden = true
|
||||
window?.standardWindowButton(.zoomButton)?.isHidden = true
|
||||
guard let installingVersion = Bundle.main.infoDictionary?[kCFBundleVersionKey as String] as? String,
|
||||
let versionString = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String else {
|
||||
guard
|
||||
let installingVersion = Bundle.main.infoDictionary?[kCFBundleVersionKey as String]
|
||||
as? String,
|
||||
let versionString = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
|
||||
else {
|
||||
return
|
||||
}
|
||||
if let copyrightLabel = Bundle.main.localizedInfoDictionary?["NSHumanReadableCopyright"] as? String {
|
||||
if let copyrightLabel = Bundle.main.localizedInfoDictionary?["NSHumanReadableCopyright"]
|
||||
as? String
|
||||
{
|
||||
appCopyrightLabel.stringValue = copyrightLabel
|
||||
}
|
||||
if let eulaContent = Bundle.main.localizedInfoDictionary?["CFEULAContent"] as? String {
|
||||
appEULAContent.string = eulaContent
|
||||
}
|
||||
appVersionLabel.stringValue = String(format: "%@ Build %@", versionString, installingVersion)
|
||||
appVersionLabel.stringValue = String(
|
||||
format: "%@ Build %@", versionString, installingVersion)
|
||||
}
|
||||
|
||||
@IBAction func btnWiki(_ sender: NSButton) {
|
||||
|
|
Loading…
Reference in New Issue