Repo // Refactoring InputFSM - preparation.
This commit is contained in:
parent
205e07e03a
commit
97062ab731
|
@ -0,0 +1,182 @@
|
||||||
|
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||||
|
// ====================
|
||||||
|
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
|
||||||
|
// ... with NTL restriction stating that:
|
||||||
|
// 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 defined in MIT License.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
// 用以讓每個狀態自描述的 enum。
|
||||||
|
public enum StateType {
|
||||||
|
case ofDeactivated
|
||||||
|
case ofEmpty
|
||||||
|
case ofAbortion // 該狀態會自動轉為 Empty
|
||||||
|
case ofCommitting
|
||||||
|
case ofAssociates
|
||||||
|
case ofNotEmpty
|
||||||
|
case ofInputting
|
||||||
|
case ofMarking
|
||||||
|
case ofCandidates
|
||||||
|
case ofSymbolTable
|
||||||
|
}
|
||||||
|
|
||||||
|
// 所有 InputState 均遵守該協定:
|
||||||
|
public protocol InputStateProtocol {
|
||||||
|
var type: StateType { get }
|
||||||
|
var data: StateData { get }
|
||||||
|
var hasBuffer: Bool { get }
|
||||||
|
var isCandidateContainer: Bool { get }
|
||||||
|
var displayedText: String { get }
|
||||||
|
var textToCommit: String { get set }
|
||||||
|
var tooltip: String { get set }
|
||||||
|
var attributedString: NSAttributedString { get }
|
||||||
|
var node: SymbolNode { get set }
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct IMEState {
|
||||||
|
public var type: StateType = .ofEmpty
|
||||||
|
public var data: StateData = .init()
|
||||||
|
init(_ data: StateData = .init(), type: StateType = .ofEmpty) {
|
||||||
|
self.data = data
|
||||||
|
self.type = type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - 針對不同的狀態,規定不同的構造器
|
||||||
|
|
||||||
|
extension IMEState {
|
||||||
|
public static func Deactivated() -> IMEState { .init(type: .ofDeactivated) }
|
||||||
|
public static func Empty() -> IMEState { .init(type: .ofEmpty) }
|
||||||
|
public static func Abortion() -> IMEState { .init(type: .ofAbortion) }
|
||||||
|
public static func Committing(textToCommit: String) -> IMEState {
|
||||||
|
var result = IMEState(type: .ofCommitting)
|
||||||
|
result.data.textToCommit = textToCommit
|
||||||
|
ChineseConverter.ensureCurrencyNumerals(target: &result.data.textToCommit)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func Associates(candidates: [(String, String)]) -> IMEState {
|
||||||
|
var result = IMEState(type: .ofAssociates)
|
||||||
|
result.data.candidates = candidates
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func NotEmpty(nodeValues: [String], reading: String = "", cursor: Int) -> IMEState {
|
||||||
|
var result = IMEState(type: .ofNotEmpty)
|
||||||
|
// 注意資料的設定順序:nodeValuesArray 必須比 reading 先設定。
|
||||||
|
result.data.nodeValuesArray = nodeValues
|
||||||
|
if !reading.isEmpty {
|
||||||
|
result.data.reading = reading // 會在被寫入資料值後自動更新 nodeValuesArray
|
||||||
|
}
|
||||||
|
// 此時 nodeValuesArray 已經被塞上讀音,直接使用即可。
|
||||||
|
result.data.displayedText = result.data.nodeValuesArray.joined()
|
||||||
|
result.data.cursor = cursor
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func Inputting(nodeValues: [String], reading: String = "", cursor: Int) -> IMEState {
|
||||||
|
var result = IMEState.NotEmpty(nodeValues: nodeValues, reading: reading, cursor: cursor)
|
||||||
|
result.type = .ofInputting
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func Marking(nodeValues: [String], nodeReadings: [String], cursor: Int, marker: Int) -> IMEState {
|
||||||
|
var result = IMEState.NotEmpty(nodeValues: nodeValues, cursor: cursor)
|
||||||
|
result.type = .ofMarking
|
||||||
|
result.data.nodeReadingsArray = nodeReadings
|
||||||
|
result.data.marker = marker
|
||||||
|
StateData.Marking.updateParameters(&result.data)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func Candidates(candidates: [(String, String)], nodeValues: [String], cursor: Int) -> IMEState {
|
||||||
|
var result = IMEState.NotEmpty(nodeValues: nodeValues, cursor: cursor)
|
||||||
|
result.type = .ofCandidates
|
||||||
|
result.data.candidates = candidates
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func SymbolTable(node: SymbolNode, previous: SymbolNode? = nil) -> IMEState {
|
||||||
|
let candidates = { node.children?.map(\.title) ?? [String]() }().map { ("", $0) }
|
||||||
|
var result = IMEState.Candidates(candidates: candidates, nodeValues: [], cursor: 0)
|
||||||
|
result.type = .ofSymbolTable
|
||||||
|
result.data.node = node
|
||||||
|
if let previous = previous {
|
||||||
|
result.data.node.previous = previous
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - 規定一個狀態該怎樣返回自己的資料值
|
||||||
|
|
||||||
|
extension IMEState: InputStateProtocol {
|
||||||
|
public var convertedToInputting: IMEState {
|
||||||
|
if type == .ofInputting { return self }
|
||||||
|
var result = IMEState.Inputting(nodeValues: data.nodeValuesArray, reading: data.reading, cursor: data.cursor)
|
||||||
|
result.tooltip = data.tooltipBackupForInputting
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
public var textToCommit: String {
|
||||||
|
get {
|
||||||
|
data.textToCommit
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
data.textToCommit = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var tooltip: String {
|
||||||
|
get {
|
||||||
|
data.tooltip
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
data.tooltip = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var attributedString: NSAttributedString {
|
||||||
|
switch type {
|
||||||
|
case .ofMarking: return data.attributedStringMarking
|
||||||
|
case .ofAssociates, .ofSymbolTable: return data.attributedStringPlaceholder
|
||||||
|
default: return data.attributedStringNormal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var node: SymbolNode {
|
||||||
|
get {
|
||||||
|
data.node
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
data.node = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var tooltipBackupForInputting: String {
|
||||||
|
get {
|
||||||
|
data.tooltipBackupForInputting
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
data.tooltipBackupForInputting = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var hasBuffer: Bool {
|
||||||
|
switch type {
|
||||||
|
case .ofNotEmpty, .ofInputting, .ofMarking, .ofCandidates: return true
|
||||||
|
default: return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var isCandidateContainer: Bool {
|
||||||
|
switch type {
|
||||||
|
case .ofCandidates, .ofAssociates, .ofSymbolTable: return true
|
||||||
|
default: return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var displayedText: String { data.displayedText }
|
||||||
|
}
|
|
@ -0,0 +1,268 @@
|
||||||
|
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||||
|
// ====================
|
||||||
|
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
|
||||||
|
// ... with NTL restriction stating that:
|
||||||
|
// 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 defined in MIT License.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public struct StateData {
|
||||||
|
var displayedText: String = "" {
|
||||||
|
didSet {
|
||||||
|
let result = IME.kanjiConversionIfRequired(displayedText)
|
||||||
|
if result.utf16.count == displayedText.utf16.count, result.count == displayedText.count {
|
||||||
|
displayedText = result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Cursor & Marker & Range for UTF8
|
||||||
|
|
||||||
|
var cursor: Int = 0 {
|
||||||
|
didSet {
|
||||||
|
cursor = min(max(cursor, 0), displayedText.count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var marker: Int = 0 {
|
||||||
|
didSet {
|
||||||
|
marker = min(max(marker, 0), displayedText.count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var markedRange: Range<Int> {
|
||||||
|
min(cursor, marker)..<max(cursor, marker)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Cursor & Marker & Range for UTF16 (Read-Only)
|
||||||
|
|
||||||
|
var u16Cursor: Int {
|
||||||
|
displayedText.charComponents[0..<cursor].joined().utf16.count
|
||||||
|
}
|
||||||
|
|
||||||
|
var u16Marker: Int {
|
||||||
|
displayedText.charComponents[0..<marker].joined().utf16.count
|
||||||
|
}
|
||||||
|
|
||||||
|
var u16MarkedRange: Range<Int> {
|
||||||
|
min(u16Cursor, u16Marker)..<max(u16Cursor, u16Marker)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Other data for non-empty states.
|
||||||
|
|
||||||
|
var markedTargetExists: Bool = false
|
||||||
|
var nodeReadingsArray = [String]()
|
||||||
|
var nodeValuesArray = [String]()
|
||||||
|
var reading: String = "" {
|
||||||
|
didSet {
|
||||||
|
if !reading.isEmpty {
|
||||||
|
var newNodeValuesArray = [String]()
|
||||||
|
var temporaryNode = ""
|
||||||
|
var charCounter = 0
|
||||||
|
for node in nodeValuesArray {
|
||||||
|
for char in node {
|
||||||
|
if charCounter == cursor - reading.count {
|
||||||
|
newNodeValuesArray.append(temporaryNode)
|
||||||
|
temporaryNode = ""
|
||||||
|
newNodeValuesArray.append(reading)
|
||||||
|
}
|
||||||
|
temporaryNode += String(char)
|
||||||
|
charCounter += 1
|
||||||
|
}
|
||||||
|
newNodeValuesArray.append(temporaryNode)
|
||||||
|
temporaryNode = ""
|
||||||
|
}
|
||||||
|
nodeValuesArray = newNodeValuesArray.isEmpty ? [reading] : newNodeValuesArray
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var candidates = [(String, String)]()
|
||||||
|
var textToCommit: String = ""
|
||||||
|
var tooltip: String = ""
|
||||||
|
var tooltipBackupForInputting: String = ""
|
||||||
|
var attributedStringPlaceholder: NSAttributedString = .init(
|
||||||
|
string: " ",
|
||||||
|
attributes: [
|
||||||
|
.underlineStyle: NSUnderlineStyle.single.rawValue,
|
||||||
|
.markedClauseSegment: 0,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
var isFilterable: Bool {
|
||||||
|
markedTargetExists ? mgrPrefs.allowedMarkRange.contains(markedRange.count) : false
|
||||||
|
}
|
||||||
|
|
||||||
|
var readingCountMismatched: Bool { displayedText.count != nodeReadingsArray.count }
|
||||||
|
var attributedStringNormal: NSAttributedString {
|
||||||
|
/// 考慮到因為滑鼠點擊等其它行為導致的組字區內容遞交情況,
|
||||||
|
/// 這裡對組字區內容也加上康熙字轉換或者 JIS 漢字轉換處理。
|
||||||
|
let attributedString = NSMutableAttributedString(string: displayedText)
|
||||||
|
var newBegin = 0
|
||||||
|
for (i, neta) in nodeValuesArray.enumerated() {
|
||||||
|
attributedString.setAttributes(
|
||||||
|
[
|
||||||
|
/// 不能用 .thick,否則會看不到游標。
|
||||||
|
.underlineStyle: NSUnderlineStyle.single.rawValue,
|
||||||
|
.markedClauseSegment: i,
|
||||||
|
], range: NSRange(location: newBegin, length: neta.utf16.count)
|
||||||
|
)
|
||||||
|
newBegin += neta.utf16.count
|
||||||
|
}
|
||||||
|
return attributedString
|
||||||
|
}
|
||||||
|
|
||||||
|
var attributedStringMarking: NSAttributedString {
|
||||||
|
/// 考慮到因為滑鼠點擊等其它行為導致的組字區內容遞交情況,
|
||||||
|
/// 這裡對組字區內容也加上康熙字轉換或者 JIS 漢字轉換處理。
|
||||||
|
let attributedString = NSMutableAttributedString(string: displayedText)
|
||||||
|
let end = u16MarkedRange.upperBound
|
||||||
|
|
||||||
|
attributedString.setAttributes(
|
||||||
|
[
|
||||||
|
.underlineStyle: NSUnderlineStyle.single.rawValue,
|
||||||
|
.markedClauseSegment: 0,
|
||||||
|
], range: NSRange(location: 0, length: u16MarkedRange.lowerBound)
|
||||||
|
)
|
||||||
|
attributedString.setAttributes(
|
||||||
|
[
|
||||||
|
.underlineStyle: NSUnderlineStyle.thick.rawValue,
|
||||||
|
.markedClauseSegment: 1,
|
||||||
|
],
|
||||||
|
range: NSRange(
|
||||||
|
location: u16MarkedRange.lowerBound,
|
||||||
|
length: u16MarkedRange.upperBound - u16MarkedRange.lowerBound
|
||||||
|
)
|
||||||
|
)
|
||||||
|
attributedString.setAttributes(
|
||||||
|
[
|
||||||
|
.underlineStyle: NSUnderlineStyle.single.rawValue,
|
||||||
|
.markedClauseSegment: 2,
|
||||||
|
],
|
||||||
|
range: NSRange(
|
||||||
|
location: end,
|
||||||
|
length: displayedText.utf16.count - end
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return attributedString
|
||||||
|
}
|
||||||
|
|
||||||
|
var node: SymbolNode = .init("")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - InputState 工具函式
|
||||||
|
|
||||||
|
extension StateData {
|
||||||
|
var chkIfUserPhraseExists: Bool {
|
||||||
|
let text = displayedText.charComponents[markedRange].joined()
|
||||||
|
let selectedReadings = nodeReadingsArray[markedRange]
|
||||||
|
let joined = selectedReadings.joined(separator: "-")
|
||||||
|
return mgrLangModel.checkIfUserPhraseExist(
|
||||||
|
userPhrase: text, mode: IME.currentInputMode, key: joined
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
var userPhrase: String {
|
||||||
|
let text = displayedText.charComponents[markedRange].joined()
|
||||||
|
let selectedReadings = nodeReadingsArray[markedRange]
|
||||||
|
let joined = selectedReadings.joined(separator: "-")
|
||||||
|
let nerfedScore = ctlInputMethod.areWeNerfing && markedTargetExists ? " -114.514" : ""
|
||||||
|
return "\(text) \(joined)\(nerfedScore)"
|
||||||
|
}
|
||||||
|
|
||||||
|
var userPhraseConverted: String {
|
||||||
|
let text =
|
||||||
|
ChineseConverter.crossConvert(displayedText.charComponents[markedRange].joined()) ?? ""
|
||||||
|
let selectedReadings = nodeReadingsArray[markedRange]
|
||||||
|
let joined = selectedReadings.joined(separator: "-")
|
||||||
|
let nerfedScore = ctlInputMethod.areWeNerfing && markedTargetExists ? " -114.514" : ""
|
||||||
|
let convertedMark = "#𝙃𝙪𝙢𝙖𝙣𝘾𝙝𝙚𝙘𝙠𝙍𝙚𝙦𝙪𝙞𝙧𝙚𝙙"
|
||||||
|
return "\(text) \(joined)\(nerfedScore)\t\(convertedMark)"
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Marking {
|
||||||
|
private static func generateReadingThread(_ data: StateData) -> String {
|
||||||
|
var arrOutput = [String]()
|
||||||
|
for neta in data.nodeReadingsArray[data.markedRange] {
|
||||||
|
var neta = neta
|
||||||
|
if neta.isEmpty { continue }
|
||||||
|
if neta.contains("_") {
|
||||||
|
arrOutput.append("??")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if mgrPrefs.showHanyuPinyinInCompositionBuffer { // 恢復陰平標記->注音轉拼音->轉教科書式標調
|
||||||
|
neta = Tekkon.restoreToneOneInZhuyinKey(target: neta)
|
||||||
|
neta = Tekkon.cnvPhonaToHanyuPinyin(target: neta)
|
||||||
|
neta = Tekkon.cnvHanyuPinyinToTextbookStyle(target: neta)
|
||||||
|
} else {
|
||||||
|
neta = Tekkon.cnvZhuyinChainToTextbookReading(target: neta)
|
||||||
|
}
|
||||||
|
arrOutput.append(neta)
|
||||||
|
}
|
||||||
|
return arrOutput.joined(separator: " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 更新工具提示內容、以及對應配對是否在庫。
|
||||||
|
/// - Parameter data: 要處理的狀態資料包。
|
||||||
|
public static func updateParameters(_ data: inout StateData) {
|
||||||
|
var tooltipGenerated: String {
|
||||||
|
if data.displayedText.count != data.nodeReadingsArray.count {
|
||||||
|
ctlInputMethod.tooltipController.setColor(state: .redAlert)
|
||||||
|
return NSLocalizedString(
|
||||||
|
"⚠︎ Unhandlable: Chars and Readings in buffer doesn't match.", comment: ""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if mgrPrefs.phraseReplacementEnabled {
|
||||||
|
ctlInputMethod.tooltipController.setColor(state: .warning)
|
||||||
|
return NSLocalizedString(
|
||||||
|
"⚠︎ Phrase replacement mode enabled, interfering user phrase entry.", comment: ""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if data.markedRange.isEmpty {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
let text = data.displayedText.charComponents[data.markedRange].joined()
|
||||||
|
if data.markedRange.count < mgrPrefs.allowedMarkRange.lowerBound {
|
||||||
|
ctlInputMethod.tooltipController.setColor(state: .denialInsufficiency)
|
||||||
|
return String(
|
||||||
|
format: NSLocalizedString(
|
||||||
|
"\"%@\" length must ≥ 2 for a user phrase.", comment: ""
|
||||||
|
) + "\n// " + generateReadingThread(data), text
|
||||||
|
)
|
||||||
|
} else if data.markedRange.count > mgrPrefs.allowedMarkRange.upperBound {
|
||||||
|
ctlInputMethod.tooltipController.setColor(state: .denialOverflow)
|
||||||
|
return String(
|
||||||
|
format: NSLocalizedString(
|
||||||
|
"\"%@\" length should ≤ %d for a user phrase.", comment: ""
|
||||||
|
) + "\n// " + generateReadingThread(data), text, mgrPrefs.allowedMarkRange.upperBound
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let selectedReadings = data.nodeReadingsArray[data.markedRange]
|
||||||
|
let joined = selectedReadings.joined(separator: "-")
|
||||||
|
let exist = mgrLangModel.checkIfUserPhraseExist(
|
||||||
|
userPhrase: text, mode: IME.currentInputMode, key: joined
|
||||||
|
)
|
||||||
|
if exist {
|
||||||
|
data.markedTargetExists = exist
|
||||||
|
ctlInputMethod.tooltipController.setColor(state: .prompt)
|
||||||
|
return String(
|
||||||
|
format: NSLocalizedString(
|
||||||
|
"\"%@\" already exists: ENTER to boost, SHIFT+COMMAND+ENTER to nerf, \n BackSpace or Delete key to exclude.",
|
||||||
|
comment: ""
|
||||||
|
) + "\n// " + generateReadingThread(data), text
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ctlInputMethod.tooltipController.resetColor()
|
||||||
|
return String(
|
||||||
|
format: NSLocalizedString("\"%@\" selected. ENTER to add user phrase.", comment: "") + "\n// "
|
||||||
|
+ generateReadingThread(data),
|
||||||
|
text
|
||||||
|
)
|
||||||
|
}
|
||||||
|
data.tooltip = tooltipGenerated
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,27 +12,6 @@ import Foundation
|
||||||
|
|
||||||
// 註:所有 InputState 型別均不適合使用 Struct,因為 Struct 無法相互繼承派生。
|
// 註:所有 InputState 型別均不適合使用 Struct,因為 Struct 無法相互繼承派生。
|
||||||
|
|
||||||
// 用以讓每個狀態自描述的 enum。
|
|
||||||
public enum StateType {
|
|
||||||
case ofDeactivated
|
|
||||||
case ofAssociatedPhrases
|
|
||||||
case ofEmpty
|
|
||||||
case ofEmptyIgnoringPreviousState
|
|
||||||
case ofCommitting
|
|
||||||
case ofNotEmpty
|
|
||||||
case ofInputting
|
|
||||||
case ofMarking
|
|
||||||
case ofChoosingCandidate
|
|
||||||
case ofSymbolTable
|
|
||||||
}
|
|
||||||
|
|
||||||
// 所有 InputState 均遵守該協定:
|
|
||||||
public protocol InputStateProtocol {
|
|
||||||
var type: StateType { get }
|
|
||||||
var hasBuffer: Bool { get }
|
|
||||||
var isCandidateContainer: Bool { get }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 此型別用以呈現輸入法控制器(ctlInputMethod)的各種狀態。
|
/// 此型別用以呈現輸入法控制器(ctlInputMethod)的各種狀態。
|
||||||
///
|
///
|
||||||
/// 從實際角度來看,輸入法屬於有限態械(Finite State Machine)。其藉由滑鼠/鍵盤
|
/// 從實際角度來看,輸入法屬於有限態械(Finite State Machine)。其藉由滑鼠/鍵盤
|
||||||
|
@ -56,7 +35,7 @@ public protocol InputStateProtocol {
|
||||||
/// 組字區內存入任何東西,所以該狀態不受 .NotEmpty 的管轄。
|
/// 組字區內存入任何東西,所以該狀態不受 .NotEmpty 的管轄。
|
||||||
/// - .Empty: 使用者剛剛切換至該輸入法、卻還沒有任何輸入行為。抑或是剛剛敲字遞交給
|
/// - .Empty: 使用者剛剛切換至該輸入法、卻還沒有任何輸入行為。抑或是剛剛敲字遞交給
|
||||||
/// 客體應用、準備新的輸入行為。
|
/// 客體應用、準備新的輸入行為。
|
||||||
/// - .EmptyIgnoringPreviousState: 與 Empty 類似,但會扔掉上一個狀態的內容、不將這些
|
/// - .Abortion: 與 Empty 類似,但會扔掉上一個狀態的內容、不將這些
|
||||||
/// 內容遞交給客體應用。該狀態在處理完畢之後會被立刻切換至 .Empty()。
|
/// 內容遞交給客體應用。該狀態在處理完畢之後會被立刻切換至 .Empty()。
|
||||||
/// - .Committing: 該狀態會承載要遞交出去的內容,讓輸入法控制器處理時代為遞交。
|
/// - .Committing: 該狀態會承載要遞交出去的內容,讓輸入法控制器處理時代為遞交。
|
||||||
/// - .NotEmpty: 非空狀態,是一種狀態大類、用以派生且代表下述諸狀態。
|
/// - .NotEmpty: 非空狀態,是一種狀態大類、用以派生且代表下述諸狀態。
|
||||||
|
@ -68,6 +47,12 @@ public protocol InputStateProtocol {
|
||||||
public enum InputState {
|
public enum InputState {
|
||||||
/// .Deactivated: 使用者沒在使用輸入法。
|
/// .Deactivated: 使用者沒在使用輸入法。
|
||||||
class Deactivated: InputStateProtocol {
|
class Deactivated: InputStateProtocol {
|
||||||
|
var node: SymbolNode = .init("")
|
||||||
|
var attributedString: NSAttributedString = .init()
|
||||||
|
var data: StateData = .init()
|
||||||
|
var textToCommit: String = ""
|
||||||
|
var tooltip: String = ""
|
||||||
|
let displayedText: String = ""
|
||||||
let hasBuffer: Bool = false
|
let hasBuffer: Bool = false
|
||||||
let isCandidateContainer: Bool = false
|
let isCandidateContainer: Bool = false
|
||||||
public var type: StateType { .ofDeactivated }
|
public var type: StateType { .ofDeactivated }
|
||||||
|
@ -78,32 +63,41 @@ public enum InputState {
|
||||||
/// .Empty: 使用者剛剛切換至該輸入法、卻還沒有任何輸入行為。
|
/// .Empty: 使用者剛剛切換至該輸入法、卻還沒有任何輸入行為。
|
||||||
/// 抑或是剛剛敲字遞交給客體應用、準備新的輸入行為。
|
/// 抑或是剛剛敲字遞交給客體應用、準備新的輸入行為。
|
||||||
class Empty: InputStateProtocol {
|
class Empty: InputStateProtocol {
|
||||||
|
var node: SymbolNode = .init("")
|
||||||
|
var attributedString: NSAttributedString = .init()
|
||||||
|
var data: StateData = .init()
|
||||||
|
var textToCommit: String = ""
|
||||||
|
var tooltip: String = ""
|
||||||
let hasBuffer: Bool = false
|
let hasBuffer: Bool = false
|
||||||
let isCandidateContainer: Bool = false
|
let isCandidateContainer: Bool = false
|
||||||
public var type: StateType { .ofEmpty }
|
public var type: StateType { .ofEmpty }
|
||||||
let composingBuffer: String = ""
|
let displayedText: String = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: -
|
// MARK: -
|
||||||
|
|
||||||
/// .EmptyIgnoringPreviousState: 與 Empty 類似,
|
/// .Abortion: 與 Empty 類似,
|
||||||
/// 但會扔掉上一個狀態的內容、不將這些內容遞交給客體應用。
|
/// 但會扔掉上一個狀態的內容、不將這些內容遞交給客體應用。
|
||||||
/// 該狀態在處理完畢之後會被立刻切換至 .Empty()。
|
/// 該狀態在處理完畢之後會被立刻切換至 .Empty()。
|
||||||
class EmptyIgnoringPreviousState: Empty {
|
class Abortion: Empty {
|
||||||
override public var type: StateType { .ofEmptyIgnoringPreviousState }
|
override public var type: StateType { .ofAbortion }
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: -
|
// MARK: -
|
||||||
|
|
||||||
/// .Committing: 該狀態會承載要遞交出去的內容,讓輸入法控制器處理時代為遞交。
|
/// .Committing: 該狀態會承載要遞交出去的內容,讓輸入法控制器處理時代為遞交。
|
||||||
class Committing: InputStateProtocol {
|
class Committing: InputStateProtocol {
|
||||||
|
var node: SymbolNode = .init("")
|
||||||
|
var attributedString: NSAttributedString = .init()
|
||||||
|
var data: StateData = .init()
|
||||||
|
var tooltip: String = ""
|
||||||
|
var textToCommit: String = ""
|
||||||
|
let displayedText: String = ""
|
||||||
let hasBuffer: Bool = false
|
let hasBuffer: Bool = false
|
||||||
let isCandidateContainer: Bool = false
|
let isCandidateContainer: Bool = false
|
||||||
public var type: StateType { .ofCommitting }
|
public var type: StateType { .ofCommitting }
|
||||||
private(set) var textToCommit: String = ""
|
|
||||||
|
|
||||||
convenience init(textToCommit: String) {
|
init(textToCommit: String) {
|
||||||
self.init()
|
|
||||||
self.textToCommit = textToCommit
|
self.textToCommit = textToCommit
|
||||||
ChineseConverter.ensureCurrencyNumerals(target: &self.textToCommit)
|
ChineseConverter.ensureCurrencyNumerals(target: &self.textToCommit)
|
||||||
}
|
}
|
||||||
|
@ -113,26 +107,29 @@ public enum InputState {
|
||||||
|
|
||||||
/// .AssociatedPhrases: 逐字選字模式內的聯想詞輸入狀態。
|
/// .AssociatedPhrases: 逐字選字模式內的聯想詞輸入狀態。
|
||||||
/// 因為逐字選字模式不需要在組字區內存入任何東西,所以該狀態不受 .NotEmpty 的管轄。
|
/// 因為逐字選字模式不需要在組字區內存入任何東西,所以該狀態不受 .NotEmpty 的管轄。
|
||||||
class AssociatedPhrases: InputStateProtocol {
|
class Associates: InputStateProtocol {
|
||||||
|
var node: SymbolNode = .init("")
|
||||||
|
var attributedString: NSAttributedString = .init()
|
||||||
|
var data: StateData = .init()
|
||||||
|
var textToCommit: String = ""
|
||||||
|
var tooltip: String = ""
|
||||||
|
let displayedText: String = ""
|
||||||
let hasBuffer: Bool = false
|
let hasBuffer: Bool = false
|
||||||
let isCandidateContainer: Bool = true
|
let isCandidateContainer: Bool = true
|
||||||
public var type: StateType { .ofAssociatedPhrases }
|
public var type: StateType { .ofAssociates }
|
||||||
private(set) var candidates: [(String, String)] = []
|
var candidates: [(String, String)] { data.candidates }
|
||||||
private(set) var isTypingVertical: Bool = false
|
init(candidates: [(String, String)]) {
|
||||||
init(candidates: [(String, String)], isTypingVertical: Bool) {
|
data.candidates = candidates
|
||||||
self.candidates = candidates
|
attributedString = {
|
||||||
self.isTypingVertical = isTypingVertical
|
let attributedString = NSMutableAttributedString(
|
||||||
}
|
string: " ",
|
||||||
|
attributes: [
|
||||||
var attributedString: NSMutableAttributedString {
|
.underlineStyle: NSUnderlineStyle.single.rawValue,
|
||||||
let attributedString = NSMutableAttributedString(
|
.markedClauseSegment: 0,
|
||||||
string: " ",
|
]
|
||||||
attributes: [
|
)
|
||||||
.underlineStyle: NSUnderlineStyle.single.rawValue,
|
return attributedString
|
||||||
.markedClauseSegment: 0,
|
}()
|
||||||
]
|
|
||||||
)
|
|
||||||
return attributedString
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,27 +142,32 @@ public enum InputState {
|
||||||
/// - .ChoosingCandidate: 叫出選字窗、允許使用者選字。
|
/// - .ChoosingCandidate: 叫出選字窗、允許使用者選字。
|
||||||
/// - .SymbolTable: 波浪鍵符號選單專用的狀態,有自身的特殊處理。
|
/// - .SymbolTable: 波浪鍵符號選單專用的狀態,有自身的特殊處理。
|
||||||
class NotEmpty: InputStateProtocol {
|
class NotEmpty: InputStateProtocol {
|
||||||
|
var node: SymbolNode = .init("")
|
||||||
|
var attributedString: NSAttributedString = .init()
|
||||||
|
var data: StateData = .init()
|
||||||
|
var tooltip: String = ""
|
||||||
|
var textToCommit: String = ""
|
||||||
let hasBuffer: Bool = true
|
let hasBuffer: Bool = true
|
||||||
var isCandidateContainer: Bool { false }
|
var isCandidateContainer: Bool { false }
|
||||||
public var type: StateType { .ofNotEmpty }
|
public var type: StateType { .ofNotEmpty }
|
||||||
private(set) var composingBuffer: String
|
private(set) var displayedText: String
|
||||||
private(set) var cursorIndex: Int = 0 { didSet { cursorIndex = max(cursorIndex, 0) } }
|
private(set) var cursorIndex: Int = 0 { didSet { cursorIndex = max(cursorIndex, 0) } }
|
||||||
private(set) var reading: String = ""
|
private(set) var reading: String = ""
|
||||||
private(set) var nodeValuesArray = [String]()
|
private(set) var nodeValuesArray = [String]()
|
||||||
public var composingBufferConverted: String {
|
public var displayedTextConverted: String {
|
||||||
let converted = IME.kanjiConversionIfRequired(composingBuffer)
|
let converted = IME.kanjiConversionIfRequired(displayedText)
|
||||||
if converted.utf16.count != composingBuffer.utf16.count
|
if converted.utf16.count != displayedText.utf16.count
|
||||||
|| converted.count != composingBuffer.count
|
|| converted.count != displayedText.count
|
||||||
{
|
{
|
||||||
return composingBuffer
|
return displayedText
|
||||||
}
|
}
|
||||||
return converted
|
return converted
|
||||||
}
|
}
|
||||||
|
|
||||||
public var committingBufferConverted: String { composingBufferConverted }
|
public var committingBufferConverted: String { displayedTextConverted }
|
||||||
|
|
||||||
init(composingBuffer: String, cursorIndex: Int, reading: String = "", nodeValuesArray: [String] = []) {
|
init(displayedText: String, cursorIndex: Int, reading: String = "", nodeValuesArray: [String] = []) {
|
||||||
self.composingBuffer = composingBuffer
|
self.displayedText = displayedText
|
||||||
self.reading = reading
|
self.reading = reading
|
||||||
// 為了簡化運算,將 reading 本身也變成一個字詞節點。
|
// 為了簡化運算,將 reading 本身也變成一個字詞節點。
|
||||||
if !reading.isEmpty {
|
if !reading.isEmpty {
|
||||||
|
@ -189,25 +191,26 @@ public enum InputState {
|
||||||
} else {
|
} else {
|
||||||
self.nodeValuesArray = nodeValuesArray
|
self.nodeValuesArray = nodeValuesArray
|
||||||
}
|
}
|
||||||
defer { self.cursorIndex = cursorIndex }
|
defer {
|
||||||
}
|
self.cursorIndex = cursorIndex
|
||||||
|
self.attributedString = {
|
||||||
var attributedString: NSMutableAttributedString {
|
/// 考慮到因為滑鼠點擊等其它行為導致的組字區內容遞交情況,
|
||||||
/// 考慮到因為滑鼠點擊等其它行為導致的組字區內容遞交情況,
|
/// 這裡對組字區內容也加上康熙字轉換或者 JIS 漢字轉換處理。
|
||||||
/// 這裡對組字區內容也加上康熙字轉換或者 JIS 漢字轉換處理。
|
let attributedString = NSMutableAttributedString(string: displayedTextConverted)
|
||||||
let attributedString = NSMutableAttributedString(string: composingBufferConverted)
|
var newBegin = 0
|
||||||
var newBegin = 0
|
for (i, neta) in nodeValuesArray.enumerated() {
|
||||||
for (i, neta) in nodeValuesArray.enumerated() {
|
attributedString.setAttributes(
|
||||||
attributedString.setAttributes(
|
[
|
||||||
[
|
/// 不能用 .thick,否則會看不到游標。
|
||||||
/// 不能用 .thick,否則會看不到游標。
|
.underlineStyle: NSUnderlineStyle.single.rawValue,
|
||||||
.underlineStyle: NSUnderlineStyle.single.rawValue,
|
.markedClauseSegment: i,
|
||||||
.markedClauseSegment: i,
|
], range: NSRange(location: newBegin, length: neta.utf16.count)
|
||||||
], range: NSRange(location: newBegin, length: neta.utf16.count)
|
)
|
||||||
)
|
newBegin += neta.utf16.count
|
||||||
newBegin += neta.utf16.count
|
}
|
||||||
|
return attributedString
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
return attributedString
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,23 +219,20 @@ public enum InputState {
|
||||||
/// .Inputting: 使用者輸入了內容。此時會出現組字區(Compositor)。
|
/// .Inputting: 使用者輸入了內容。此時會出現組字區(Compositor)。
|
||||||
class Inputting: NotEmpty {
|
class Inputting: NotEmpty {
|
||||||
override public var type: StateType { .ofInputting }
|
override public var type: StateType { .ofInputting }
|
||||||
var textToCommit: String = ""
|
|
||||||
var tooltip: String = ""
|
|
||||||
|
|
||||||
override public var committingBufferConverted: String {
|
override public var committingBufferConverted: String {
|
||||||
let committingBuffer = nodeValuesArray.joined()
|
let committingBuffer = nodeValuesArray.joined()
|
||||||
let converted = IME.kanjiConversionIfRequired(committingBuffer)
|
let converted = IME.kanjiConversionIfRequired(committingBuffer)
|
||||||
if converted.utf16.count != composingBuffer.utf16.count
|
if converted.utf16.count != displayedText.utf16.count
|
||||||
|| converted.count != composingBuffer.count
|
|| converted.count != displayedText.count
|
||||||
{
|
{
|
||||||
return composingBuffer
|
return displayedText
|
||||||
}
|
}
|
||||||
return converted
|
return converted
|
||||||
}
|
}
|
||||||
|
|
||||||
override init(composingBuffer: String, cursorIndex: Int, reading: String = "", nodeValuesArray: [String] = []) {
|
override init(displayedText: String, cursorIndex: Int, reading: String = "", nodeValuesArray: [String] = []) {
|
||||||
super.init(
|
super.init(
|
||||||
composingBuffer: composingBuffer, cursorIndex: cursorIndex, reading: reading, nodeValuesArray: nodeValuesArray
|
displayedText: displayedText, cursorIndex: cursorIndex, reading: reading, nodeValuesArray: nodeValuesArray
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -247,8 +247,8 @@ public enum InputState {
|
||||||
private(set) var markerIndex: Int = 0 { didSet { markerIndex = max(markerIndex, 0) } }
|
private(set) var markerIndex: Int = 0 { didSet { markerIndex = max(markerIndex, 0) } }
|
||||||
private(set) var markedRange: Range<Int>
|
private(set) var markedRange: Range<Int>
|
||||||
private var literalMarkedRange: Range<Int> {
|
private var literalMarkedRange: Range<Int> {
|
||||||
let lowerBoundLiteral = composingBuffer.charIndexLiteral(from: markedRange.lowerBound)
|
let lowerBoundLiteral = displayedText.charIndexLiteral(from: markedRange.lowerBound)
|
||||||
let upperBoundLiteral = composingBuffer.charIndexLiteral(from: markedRange.upperBound)
|
let upperBoundLiteral = displayedText.charIndexLiteral(from: markedRange.upperBound)
|
||||||
return lowerBoundLiteral..<upperBoundLiteral
|
return lowerBoundLiteral..<upperBoundLiteral
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -274,8 +274,9 @@ public enum InputState {
|
||||||
}
|
}
|
||||||
|
|
||||||
private var markedTargetExists = false
|
private var markedTargetExists = false
|
||||||
var tooltip: String {
|
|
||||||
if composingBuffer.count != readings.count {
|
var tooltipForMarking: String {
|
||||||
|
if displayedText.count != readings.count {
|
||||||
ctlInputMethod.tooltipController.setColor(state: .redAlert)
|
ctlInputMethod.tooltipController.setColor(state: .redAlert)
|
||||||
return NSLocalizedString(
|
return NSLocalizedString(
|
||||||
"⚠︎ Unhandlable: Chars and Readings in buffer doesn't match.", comment: ""
|
"⚠︎ Unhandlable: Chars and Readings in buffer doesn't match.", comment: ""
|
||||||
|
@ -291,7 +292,7 @@ public enum InputState {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
let text = composingBuffer.utf16SubString(with: markedRange)
|
let text = displayedText.utf16SubString(with: markedRange)
|
||||||
if literalMarkedRange.count < allowedMarkRange.lowerBound {
|
if literalMarkedRange.count < allowedMarkRange.lowerBound {
|
||||||
ctlInputMethod.tooltipController.setColor(state: .denialInsufficiency)
|
ctlInputMethod.tooltipController.setColor(state: .denialInsufficiency)
|
||||||
return String(
|
return String(
|
||||||
|
@ -331,71 +332,73 @@ public enum InputState {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
var tooltipForInputting: String = ""
|
var tooltipBackupForInputting: String = ""
|
||||||
private(set) var readings: [String]
|
private(set) var readings: [String]
|
||||||
|
|
||||||
init(
|
init(
|
||||||
composingBuffer: String, cursorIndex: Int, markerIndex: Int, readings: [String], nodeValuesArray: [String] = []
|
displayedText: String, cursorIndex: Int, markerIndex: Int, readings: [String], nodeValuesArray: [String] = []
|
||||||
) {
|
) {
|
||||||
let begin = min(cursorIndex, markerIndex)
|
let begin = min(cursorIndex, markerIndex)
|
||||||
let end = max(cursorIndex, markerIndex)
|
let end = max(cursorIndex, markerIndex)
|
||||||
markedRange = begin..<end
|
markedRange = begin..<end
|
||||||
self.readings = readings
|
self.readings = readings
|
||||||
super.init(
|
super.init(
|
||||||
composingBuffer: composingBuffer, cursorIndex: cursorIndex, nodeValuesArray: nodeValuesArray
|
displayedText: displayedText, cursorIndex: cursorIndex, nodeValuesArray: nodeValuesArray
|
||||||
)
|
)
|
||||||
defer { self.markerIndex = markerIndex }
|
defer {
|
||||||
}
|
self.markerIndex = markerIndex
|
||||||
|
tooltip = tooltipForMarking
|
||||||
|
attributedString = {
|
||||||
|
/// 考慮到因為滑鼠點擊等其它行為導致的組字區內容遞交情況,
|
||||||
|
/// 這裡對組字區內容也加上康熙字轉換或者 JIS 漢字轉換處理。
|
||||||
|
let attributedString = NSMutableAttributedString(string: displayedTextConverted)
|
||||||
|
let end = markedRange.upperBound
|
||||||
|
|
||||||
override var attributedString: NSMutableAttributedString {
|
attributedString.setAttributes(
|
||||||
/// 考慮到因為滑鼠點擊等其它行為導致的組字區內容遞交情況,
|
[
|
||||||
/// 這裡對組字區內容也加上康熙字轉換或者 JIS 漢字轉換處理。
|
.underlineStyle: NSUnderlineStyle.single.rawValue,
|
||||||
let attributedString = NSMutableAttributedString(string: composingBufferConverted)
|
.markedClauseSegment: 0,
|
||||||
let end = markedRange.upperBound
|
], range: NSRange(location: 0, length: markedRange.lowerBound)
|
||||||
|
)
|
||||||
attributedString.setAttributes(
|
attributedString.setAttributes(
|
||||||
[
|
[
|
||||||
.underlineStyle: NSUnderlineStyle.single.rawValue,
|
.underlineStyle: NSUnderlineStyle.thick.rawValue,
|
||||||
.markedClauseSegment: 0,
|
.markedClauseSegment: 1,
|
||||||
], range: NSRange(location: 0, length: markedRange.lowerBound)
|
],
|
||||||
)
|
range: NSRange(
|
||||||
attributedString.setAttributes(
|
location: markedRange.lowerBound,
|
||||||
[
|
length: markedRange.upperBound - markedRange.lowerBound
|
||||||
.underlineStyle: NSUnderlineStyle.thick.rawValue,
|
)
|
||||||
.markedClauseSegment: 1,
|
)
|
||||||
],
|
attributedString.setAttributes(
|
||||||
range: NSRange(
|
[
|
||||||
location: markedRange.lowerBound,
|
.underlineStyle: NSUnderlineStyle.single.rawValue,
|
||||||
length: markedRange.upperBound - markedRange.lowerBound
|
.markedClauseSegment: 2,
|
||||||
)
|
],
|
||||||
)
|
range: NSRange(
|
||||||
attributedString.setAttributes(
|
location: end,
|
||||||
[
|
length: displayedText.utf16.count - end
|
||||||
.underlineStyle: NSUnderlineStyle.single.rawValue,
|
)
|
||||||
.markedClauseSegment: 2,
|
)
|
||||||
],
|
return attributedString
|
||||||
range: NSRange(
|
}()
|
||||||
location: end,
|
}
|
||||||
length: composingBuffer.utf16.count - end
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return attributedString
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var convertedToInputting: Inputting {
|
var convertedToInputting: Inputting {
|
||||||
let state = Inputting(
|
let state = Inputting(
|
||||||
composingBuffer: composingBuffer, cursorIndex: cursorIndex, reading: reading, nodeValuesArray: nodeValuesArray
|
displayedText: displayedText, cursorIndex: cursorIndex, reading: reading, nodeValuesArray: nodeValuesArray
|
||||||
)
|
)
|
||||||
state.tooltip = tooltipForInputting
|
state.tooltip = tooltipBackupForInputting
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
var validToFilter: Bool { markedTargetExists ? allowedMarkRange.contains(literalMarkedRange.count) : false }
|
var isFilterable: Bool { markedTargetExists ? allowedMarkRange.contains(literalMarkedRange.count) : false }
|
||||||
|
|
||||||
var bufferReadingCountMisMatch: Bool { composingBuffer.count != readings.count }
|
var bufferReadingCountMisMatch: Bool { displayedText.count != readings.count }
|
||||||
|
|
||||||
var chkIfUserPhraseExists: Bool {
|
var chkIfUserPhraseExists: Bool {
|
||||||
let text = composingBuffer.utf16SubString(with: markedRange)
|
let text = displayedText.utf16SubString(with: markedRange)
|
||||||
let selectedReadings = readings[literalMarkedRange]
|
let selectedReadings = readings[literalMarkedRange]
|
||||||
let joined = selectedReadings.joined(separator: "-")
|
let joined = selectedReadings.joined(separator: "-")
|
||||||
return mgrLangModel.checkIfUserPhraseExist(
|
return mgrLangModel.checkIfUserPhraseExist(
|
||||||
|
@ -404,7 +407,7 @@ public enum InputState {
|
||||||
}
|
}
|
||||||
|
|
||||||
var userPhrase: String {
|
var userPhrase: String {
|
||||||
let text = composingBuffer.utf16SubString(with: markedRange)
|
let text = displayedText.utf16SubString(with: markedRange)
|
||||||
let selectedReadings = readings[literalMarkedRange]
|
let selectedReadings = readings[literalMarkedRange]
|
||||||
let joined = selectedReadings.joined(separator: "-")
|
let joined = selectedReadings.joined(separator: "-")
|
||||||
let nerfedScore = ctlInputMethod.areWeNerfing && markedTargetExists ? " -114.514" : ""
|
let nerfedScore = ctlInputMethod.areWeNerfing && markedTargetExists ? " -114.514" : ""
|
||||||
|
@ -413,7 +416,7 @@ public enum InputState {
|
||||||
|
|
||||||
var userPhraseConverted: String {
|
var userPhraseConverted: String {
|
||||||
let text =
|
let text =
|
||||||
ChineseConverter.crossConvert(composingBuffer.utf16SubString(with: markedRange)) ?? ""
|
ChineseConverter.crossConvert(displayedText.utf16SubString(with: markedRange)) ?? ""
|
||||||
let selectedReadings = readings[literalMarkedRange]
|
let selectedReadings = readings[literalMarkedRange]
|
||||||
let joined = selectedReadings.joined(separator: "-")
|
let joined = selectedReadings.joined(separator: "-")
|
||||||
let nerfedScore = ctlInputMethod.areWeNerfing && markedTargetExists ? " -114.514" : ""
|
let nerfedScore = ctlInputMethod.areWeNerfing && markedTargetExists ? " -114.514" : ""
|
||||||
|
@ -427,9 +430,8 @@ public enum InputState {
|
||||||
/// .ChoosingCandidate: 叫出選字窗、允許使用者選字。
|
/// .ChoosingCandidate: 叫出選字窗、允許使用者選字。
|
||||||
class ChoosingCandidate: NotEmpty {
|
class ChoosingCandidate: NotEmpty {
|
||||||
override var isCandidateContainer: Bool { true }
|
override var isCandidateContainer: Bool { true }
|
||||||
override public var type: StateType { .ofChoosingCandidate }
|
override public var type: StateType { .ofCandidates }
|
||||||
private(set) var candidates: [(String, String)]
|
var candidates: [(String, String)]
|
||||||
private(set) var isTypingVertical: Bool
|
|
||||||
// 該變數改為可以隨時更改的內容,不然的話 ctlInputMethod.candidateSelectionChanged() 會上演俄羅斯套娃(崩潰)。
|
// 該變數改為可以隨時更改的內容,不然的話 ctlInputMethod.candidateSelectionChanged() 會上演俄羅斯套娃(崩潰)。
|
||||||
public var chosenCandidateString: String = "" {
|
public var chosenCandidateString: String = "" {
|
||||||
didSet {
|
didSet {
|
||||||
|
@ -443,76 +445,11 @@ public enum InputState {
|
||||||
}
|
}
|
||||||
|
|
||||||
init(
|
init(
|
||||||
composingBuffer: String, cursorIndex: Int, candidates: [(String, String)], isTypingVertical: Bool,
|
displayedText: String, cursorIndex: Int, candidates: [(String, String)],
|
||||||
nodeValuesArray: [String] = []
|
nodeValuesArray: [String] = []
|
||||||
) {
|
) {
|
||||||
self.candidates = candidates
|
self.candidates = candidates
|
||||||
self.isTypingVertical = isTypingVertical
|
super.init(displayedText: displayedText, cursorIndex: cursorIndex, nodeValuesArray: nodeValuesArray)
|
||||||
super.init(composingBuffer: composingBuffer, cursorIndex: cursorIndex, nodeValuesArray: nodeValuesArray)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 這個函式尚未經過嚴格的單元測試。請在使用時確保 chosenCandidateString 為空。
|
|
||||||
// 不為空的話,該參數的返回值就會有對應的影響、顯示成類似 macOS 內建注音輸入法那樣子。
|
|
||||||
// 本來想給輸入法拓展這方面的功能的,奈何 ctlInputMethod.candidateSelectionChanged() 這函式太氣人。
|
|
||||||
// 想要講的幹話已經在那邊講完了,感興趣的可以去看看。
|
|
||||||
override var attributedString: NSMutableAttributedString {
|
|
||||||
guard !chosenCandidateString.isEmpty else { return super.attributedString }
|
|
||||||
let bufferTextRear = composingBuffer.utf16SubString(with: 0..<cursorIndex)
|
|
||||||
let bufferTextFront = composingBuffer.utf16SubString(with: cursorIndex..<(composingBuffer.utf16.count))
|
|
||||||
let cursorIndexU8 = bufferTextRear.count - 1
|
|
||||||
// 排除一些不應該出現的情形。
|
|
||||||
if (mgrPrefs.useRearCursorMode && bufferTextFront.count < chosenCandidateString.count)
|
|
||||||
|| (!mgrPrefs.useRearCursorMode && bufferTextRear.count < chosenCandidateString.count)
|
|
||||||
{
|
|
||||||
return super.attributedString
|
|
||||||
}
|
|
||||||
// u16Range 是用來畫線的,因為 NSAttributedString 只認 NSRange。
|
|
||||||
let u16Range: Range<Int> = {
|
|
||||||
switch mgrPrefs.useRearCursorMode {
|
|
||||||
case false: return (max(0, cursorIndex - chosenCandidateString.utf16.count))..<cursorIndex
|
|
||||||
case true:
|
|
||||||
return
|
|
||||||
cursorIndex..<min(cursorIndex + chosenCandidateString.utf16.count, composingBuffer.utf16.count - 1)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
// u8Range 是用來計算字串的。
|
|
||||||
let u8Range: Range<Int> = {
|
|
||||||
switch mgrPrefs.useRearCursorMode {
|
|
||||||
case false: return (max(0, cursorIndexU8 - chosenCandidateString.count))..<cursorIndexU8
|
|
||||||
case true:
|
|
||||||
return cursorIndexU8..<min(cursorIndexU8 + chosenCandidateString.count, composingBuffer.count - 1)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
let strSegmentedRear = composingBuffer.charComponents[0..<u8Range.lowerBound].joined()
|
|
||||||
let strSegmentedFront = composingBuffer.charComponents[u8Range.upperBound...].joined()
|
|
||||||
let newBufferConverted: String = NotEmpty(
|
|
||||||
composingBuffer: strSegmentedRear + chosenCandidateString + strSegmentedFront, cursorIndex: 0
|
|
||||||
).composingBufferConverted
|
|
||||||
guard newBufferConverted.count == composingBuffer.count else { return super.attributedString }
|
|
||||||
|
|
||||||
/// 考慮到因為滑鼠點擊等其它行為導致的組字區內容遞交情況,
|
|
||||||
/// 這裡對組字區內容也加上康熙字轉換或者 JIS 漢字轉換處理。
|
|
||||||
let attributedStringResult = NSMutableAttributedString(string: newBufferConverted)
|
|
||||||
attributedStringResult.setAttributes(
|
|
||||||
[
|
|
||||||
.underlineStyle: NSUnderlineStyle.single.rawValue,
|
|
||||||
.markedClauseSegment: 0,
|
|
||||||
], range: NSRange(location: 0, length: u16Range.lowerBound)
|
|
||||||
)
|
|
||||||
attributedStringResult.setAttributes(
|
|
||||||
[
|
|
||||||
.underlineStyle: NSUnderlineStyle.thick.rawValue,
|
|
||||||
.markedClauseSegment: 1,
|
|
||||||
], range: NSRange(location: u16Range.lowerBound, length: u16Range.count)
|
|
||||||
)
|
|
||||||
attributedStringResult.setAttributes(
|
|
||||||
[
|
|
||||||
.underlineStyle: NSUnderlineStyle.single.rawValue,
|
|
||||||
.markedClauseSegment: 2,
|
|
||||||
], range: NSRange(location: u16Range.upperBound, length: newBufferConverted.utf16.count)
|
|
||||||
)
|
|
||||||
|
|
||||||
return attributedStringResult
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -521,34 +458,31 @@ public enum InputState {
|
||||||
/// .SymbolTable: 波浪鍵符號選單專用的狀態,有自身的特殊處理。
|
/// .SymbolTable: 波浪鍵符號選單專用的狀態,有自身的特殊處理。
|
||||||
class SymbolTable: ChoosingCandidate {
|
class SymbolTable: ChoosingCandidate {
|
||||||
override public var type: StateType { .ofSymbolTable }
|
override public var type: StateType { .ofSymbolTable }
|
||||||
var node: SymbolNode
|
|
||||||
|
|
||||||
init(node: SymbolNode, previous: SymbolNode? = nil, isTypingVertical: Bool) {
|
init(node: SymbolNode, previous: SymbolNode? = nil) {
|
||||||
|
super.init(displayedText: "", cursorIndex: 0, candidates: [])
|
||||||
self.node = node
|
self.node = node
|
||||||
if let previous = previous {
|
if let previous = previous {
|
||||||
self.node.previous = previous
|
self.node.previous = previous
|
||||||
}
|
}
|
||||||
let candidates = node.children?.map(\.title) ?? [String]()
|
let candidates = node.children?.map(\.title) ?? [String]()
|
||||||
super.init(
|
self.candidates = candidates.map { ("", $0) }
|
||||||
composingBuffer: "", cursorIndex: 0, candidates: candidates.map { ("", $0) },
|
|
||||||
isTypingVertical: isTypingVertical
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// InputState.SymbolTable 這個狀態比較特殊,不能把真空組字區交出去。
|
// InputState.SymbolTable 這個狀態比較特殊,不能把真空組字區交出去。
|
||||||
// 不然的話,在絕大多數終端機類應用當中、以及在 MS Word 等軟體當中
|
// 不然的話,在絕大多數終端機類應用當中、以及在 MS Word 等軟體當中
|
||||||
// 會出現符號選字窗無法響應方向鍵的問題。
|
// 會出現符號選字窗無法響應方向鍵的問題。
|
||||||
// 如有誰要修奇摩注音的一點通選單的話,修復原理也是一樣的。
|
// 如有誰要修奇摩注音的一點通選單的話,修復原理也是一樣的。
|
||||||
// Crediting Qwertyyb: https://github.com/qwertyyb/Fire/issues/55#issuecomment-1133497700
|
// Crediting Qwertyyb: https://github.com/qwertyyb/Fire/issues/55#issuecomment-1133497700
|
||||||
override var attributedString: NSMutableAttributedString {
|
attributedString = {
|
||||||
let attributedString = NSMutableAttributedString(
|
let attributedString = NSMutableAttributedString(
|
||||||
string: " ",
|
string: " ",
|
||||||
attributes: [
|
attributes: [
|
||||||
.underlineStyle: NSUnderlineStyle.single.rawValue,
|
.underlineStyle: NSUnderlineStyle.single.rawValue,
|
||||||
.markedClauseSegment: 0,
|
.markedClauseSegment: 0,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
return attributedString
|
return attributedString
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@ extension KeyHandler {
|
||||||
|| ((input.isCursorBackward || input.isCursorForward) && input.isShiftHold)
|
|| ((input.isCursorBackward || input.isCursorForward) && input.isShiftHold)
|
||||||
|
|
||||||
if cancelCandidateKey {
|
if cancelCandidateKey {
|
||||||
if state is InputState.AssociatedPhrases
|
if state is InputState.Associates
|
||||||
|| mgrPrefs.useSCPCTypingMode
|
|| mgrPrefs.useSCPCTypingMode
|
||||||
|| compositor.isEmpty
|
|| compositor.isEmpty
|
||||||
{
|
{
|
||||||
|
@ -49,13 +49,13 @@ extension KeyHandler {
|
||||||
// 就將當前的組字緩衝區析構處理、強制重設輸入狀態。
|
// 就將當前的組字緩衝區析構處理、強制重設輸入狀態。
|
||||||
// 否則,一個本不該出現的真空組字緩衝區會使前後方向鍵與 BackSpace 鍵失靈。
|
// 否則,一個本不該出現的真空組字緩衝區會使前後方向鍵與 BackSpace 鍵失靈。
|
||||||
// 所以這裡需要對 compositor.isEmpty 做判定。
|
// 所以這裡需要對 compositor.isEmpty 做判定。
|
||||||
stateCallback(InputState.EmptyIgnoringPreviousState())
|
stateCallback(InputState.Abortion())
|
||||||
stateCallback(InputState.Empty())
|
stateCallback(InputState.Empty())
|
||||||
} else {
|
} else {
|
||||||
stateCallback(buildInputtingState)
|
stateCallback(buildInputtingState)
|
||||||
}
|
}
|
||||||
if let state = state as? InputState.SymbolTable, let nodePrevious = state.node.previous {
|
if let state = state as? InputState.SymbolTable, let nodePrevious = state.node.previous {
|
||||||
stateCallback(InputState.SymbolTable(node: nodePrevious, isTypingVertical: state.isTypingVertical))
|
stateCallback(InputState.SymbolTable(node: nodePrevious))
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -63,8 +63,8 @@ extension KeyHandler {
|
||||||
// MARK: Enter
|
// MARK: Enter
|
||||||
|
|
||||||
if input.isEnter {
|
if input.isEnter {
|
||||||
if state is InputState.AssociatedPhrases, !mgrPrefs.alsoConfirmAssociatedCandidatesByEnter {
|
if state is InputState.Associates, !mgrPrefs.alsoConfirmAssociatedCandidatesByEnter {
|
||||||
stateCallback(InputState.EmptyIgnoringPreviousState())
|
stateCallback(InputState.Abortion())
|
||||||
stateCallback(InputState.Empty())
|
stateCallback(InputState.Empty())
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -247,7 +247,7 @@ extension KeyHandler {
|
||||||
|
|
||||||
if let state = state as? InputState.ChoosingCandidate {
|
if let state = state as? InputState.ChoosingCandidate {
|
||||||
candidates = state.candidates
|
candidates = state.candidates
|
||||||
} else if let state = state as? InputState.AssociatedPhrases {
|
} else if let state = state as? InputState.Associates {
|
||||||
candidates = state.candidates
|
candidates = state.candidates
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -267,13 +267,13 @@ extension KeyHandler {
|
||||||
|
|
||||||
// MARK: 聯想詞處理 (Associated Phrases)
|
// MARK: 聯想詞處理 (Associated Phrases)
|
||||||
|
|
||||||
if state is InputState.AssociatedPhrases {
|
if state is InputState.Associates {
|
||||||
if !input.isShiftHold { return false }
|
if !input.isShiftHold { return false }
|
||||||
}
|
}
|
||||||
|
|
||||||
var index: Int = NSNotFound
|
var index: Int = NSNotFound
|
||||||
let match: String =
|
let match: String =
|
||||||
(state is InputState.AssociatedPhrases) ? input.inputTextIgnoringModifiers ?? "" : input.text
|
(state is InputState.Associates) ? input.inputTextIgnoringModifiers ?? "" : input.text
|
||||||
|
|
||||||
for j in 0..<ctlCandidateCurrent.keyLabels.count {
|
for j in 0..<ctlCandidateCurrent.keyLabels.count {
|
||||||
let label: CandidateKeyLabel = ctlCandidateCurrent.keyLabels[j]
|
let label: CandidateKeyLabel = ctlCandidateCurrent.keyLabels[j]
|
||||||
|
@ -293,7 +293,7 @@ extension KeyHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if state is InputState.AssociatedPhrases { return false }
|
if state is InputState.Associates { return false }
|
||||||
|
|
||||||
// MARK: 逐字選字模式的處理 (SCPC Mode Processing)
|
// MARK: 逐字選字模式的處理 (SCPC Mode Processing)
|
||||||
|
|
||||||
|
@ -333,7 +333,7 @@ extension KeyHandler {
|
||||||
didSelectCandidateAt: candidateIndex,
|
didSelectCandidateAt: candidateIndex,
|
||||||
ctlCandidate: ctlCandidateCurrent
|
ctlCandidate: ctlCandidateCurrent
|
||||||
)
|
)
|
||||||
stateCallback(InputState.EmptyIgnoringPreviousState())
|
stateCallback(InputState.Abortion())
|
||||||
stateCallback(InputState.Empty())
|
stateCallback(InputState.Empty())
|
||||||
return handle(
|
return handle(
|
||||||
input: input, state: InputState.Empty(), stateCallback: stateCallback, errorCallback: errorCallback
|
input: input, state: InputState.Empty(), stateCallback: stateCallback, errorCallback: errorCallback
|
||||||
|
|
|
@ -62,7 +62,7 @@ extension KeyHandler {
|
||||||
composer.receiveKey(fromString: input.text)
|
composer.receiveKey(fromString: input.text)
|
||||||
keyConsumedByReading = true
|
keyConsumedByReading = true
|
||||||
|
|
||||||
// 沒有調號的話,只需要 updateClientComposingBuffer() 且終止處理(return true)即可。
|
// 沒有調號的話,只需要 updateClientdisplayedText() 且終止處理(return true)即可。
|
||||||
// 有調號的話,則不需要這樣,而是轉而繼續在此之後的處理。
|
// 有調號的話,則不需要這樣,而是轉而繼續在此之後的處理。
|
||||||
if !composer.hasToneMarker() {
|
if !composer.hasToneMarker() {
|
||||||
stateCallback(buildInputtingState)
|
stateCallback(buildInputtingState)
|
||||||
|
@ -100,7 +100,7 @@ extension KeyHandler {
|
||||||
switch compositor.isEmpty {
|
switch compositor.isEmpty {
|
||||||
case false: stateCallback(buildInputtingState)
|
case false: stateCallback(buildInputtingState)
|
||||||
case true:
|
case true:
|
||||||
stateCallback(InputState.EmptyIgnoringPreviousState())
|
stateCallback(InputState.Abortion())
|
||||||
stateCallback(InputState.Empty())
|
stateCallback(InputState.Empty())
|
||||||
}
|
}
|
||||||
return true // 向 IMK 報告說這個按鍵訊號已經被輸入法攔截處理了。
|
return true // 向 IMK 報告說這個按鍵訊號已經被輸入法攔截處理了。
|
||||||
|
@ -118,7 +118,7 @@ extension KeyHandler {
|
||||||
// 之後就是更新組字區了。先清空注拼槽的內容。
|
// 之後就是更新組字區了。先清空注拼槽的內容。
|
||||||
composer.clear()
|
composer.clear()
|
||||||
|
|
||||||
// 再以回呼組字狀態的方式來執行 updateClientComposingBuffer()。
|
// 再以回呼組字狀態的方式來執行 updateClientdisplayedText()。
|
||||||
let inputting = buildInputtingState
|
let inputting = buildInputtingState
|
||||||
stateCallback(inputting)
|
stateCallback(inputting)
|
||||||
|
|
||||||
|
@ -157,7 +157,7 @@ extension KeyHandler {
|
||||||
|
|
||||||
/// 是說此時注拼槽並非為空、卻還沒組音。這種情況下只可能是「注拼槽內只有聲調」。
|
/// 是說此時注拼槽並非為空、卻還沒組音。這種情況下只可能是「注拼槽內只有聲調」。
|
||||||
if keyConsumedByReading {
|
if keyConsumedByReading {
|
||||||
// 以回呼組字狀態的方式來執行 updateClientComposingBuffer()。
|
// 以回呼組字狀態的方式來執行 updateClientdisplayedText()。
|
||||||
stateCallback(buildInputtingState)
|
stateCallback(buildInputtingState)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ extension KeyHandler {
|
||||||
// 提前過濾掉一些不合規的按鍵訊號輸入,免得相關按鍵訊號被送給 Megrez 引發輸入法崩潰。
|
// 提前過濾掉一些不合規的按鍵訊號輸入,免得相關按鍵訊號被送給 Megrez 引發輸入法崩潰。
|
||||||
if input.isInvalid {
|
if input.isInvalid {
|
||||||
// 在「.Empty(IgnoringPreviousState) 與 .Deactivated」狀態下的首次不合規按鍵輸入可以直接放行。
|
// 在「.Empty(IgnoringPreviousState) 與 .Deactivated」狀態下的首次不合規按鍵輸入可以直接放行。
|
||||||
// 因為「.EmptyIgnoringPreviousState」會在處理之後被自動轉為「.Empty」,所以不需要單獨判斷。
|
// 因為「.Abortion」會在處理之後被自動轉為「.Empty」,所以不需要單獨判斷。
|
||||||
if state is InputState.Empty || state is InputState.Deactivated {
|
if state is InputState.Empty || state is InputState.Deactivated {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ extension KeyHandler {
|
||||||
// 如果當前組字器為空的話,就不再攔截某些修飾鍵,畢竟這些鍵可能會會用來觸發某些功能。
|
// 如果當前組字器為空的話,就不再攔截某些修飾鍵,畢竟這些鍵可能會會用來觸發某些功能。
|
||||||
let isFunctionKey: Bool =
|
let isFunctionKey: Bool =
|
||||||
input.isControlHotKey || (input.isCommandHold || input.isOptionHotKey || input.isNonLaptopFunctionKey)
|
input.isControlHotKey || (input.isCommandHold || input.isOptionHotKey || input.isNonLaptopFunctionKey)
|
||||||
if !(state is InputState.NotEmpty) && !(state is InputState.AssociatedPhrases) && isFunctionKey {
|
if !(state is InputState.NotEmpty) && !(state is InputState.Associates) && isFunctionKey {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ extension KeyHandler {
|
||||||
// 不然、使用 Cocoa 內建的 flags 的話,會誤傷到在主鍵盤區域的功能鍵。
|
// 不然、使用 Cocoa 內建的 flags 的話,會誤傷到在主鍵盤區域的功能鍵。
|
||||||
// 我們先規定允許小鍵盤區域操縱選字窗,其餘場合一律直接放行。
|
// 我們先規定允許小鍵盤區域操縱選字窗,其餘場合一律直接放行。
|
||||||
if input.isNumericPadKey {
|
if input.isNumericPadKey {
|
||||||
if !(state is InputState.ChoosingCandidate || state is InputState.AssociatedPhrases
|
if !(state is InputState.ChoosingCandidate || state is InputState.Associates
|
||||||
|| state is InputState.SymbolTable)
|
|| state is InputState.SymbolTable)
|
||||||
{
|
{
|
||||||
stateCallback(InputState.Empty())
|
stateCallback(InputState.Empty())
|
||||||
|
@ -114,7 +114,7 @@ extension KeyHandler {
|
||||||
|
|
||||||
// MARK: 處理聯想詞 (Handle Associated Phrases)
|
// MARK: 處理聯想詞 (Handle Associated Phrases)
|
||||||
|
|
||||||
if state is InputState.AssociatedPhrases {
|
if state is InputState.Associates {
|
||||||
if handleCandidate(
|
if handleCandidate(
|
||||||
state: state, input: input, stateCallback: stateCallback, errorCallback: errorCallback
|
state: state, input: input, stateCallback: stateCallback, errorCallback: errorCallback
|
||||||
) {
|
) {
|
||||||
|
@ -155,9 +155,9 @@ extension KeyHandler {
|
||||||
/// 倘若沒有在偏好設定內將 Space 空格鍵設為選字窗呼叫用鍵的話………
|
/// 倘若沒有在偏好設定內將 Space 空格鍵設為選字窗呼叫用鍵的話………
|
||||||
if !mgrPrefs.chooseCandidateUsingSpace {
|
if !mgrPrefs.chooseCandidateUsingSpace {
|
||||||
if compositor.cursor >= compositor.length {
|
if compositor.cursor >= compositor.length {
|
||||||
let composingBuffer = currentState.composingBuffer
|
let displayedText = currentState.displayedText
|
||||||
if !composingBuffer.isEmpty {
|
if !displayedText.isEmpty {
|
||||||
stateCallback(InputState.Committing(textToCommit: composingBuffer))
|
stateCallback(InputState.Committing(textToCommit: displayedText))
|
||||||
}
|
}
|
||||||
stateCallback(InputState.Committing(textToCommit: " "))
|
stateCallback(InputState.Committing(textToCommit: " "))
|
||||||
stateCallback(InputState.Empty())
|
stateCallback(InputState.Empty())
|
||||||
|
|
|
@ -110,7 +110,7 @@ extension KeyHandler {
|
||||||
|
|
||||||
/// 這裡生成準備要拿來回呼的「正在輸入」狀態,但還不能立即使用,因為工具提示仍未完成。
|
/// 這裡生成準備要拿來回呼的「正在輸入」狀態,但還不能立即使用,因為工具提示仍未完成。
|
||||||
return InputState.Inputting(
|
return InputState.Inputting(
|
||||||
composingBuffer: cleanedComposition, cursorIndex: cursorIndex, reading: reading, nodeValuesArray: nodeValuesArray
|
displayedText: cleanedComposition, cursorIndex: cursorIndex, reading: reading, nodeValuesArray: nodeValuesArray
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,13 +123,12 @@ extension KeyHandler {
|
||||||
/// - Returns: 回呼一個新的選詞狀態,來就給定的候選字詞陣列資料內容顯示選字窗。
|
/// - Returns: 回呼一個新的選詞狀態,來就給定的候選字詞陣列資料內容顯示選字窗。
|
||||||
func buildCandidate(
|
func buildCandidate(
|
||||||
state currentState: InputState.NotEmpty,
|
state currentState: InputState.NotEmpty,
|
||||||
isTypingVertical: Bool = false
|
isTypingVertical _: Bool = false
|
||||||
) -> InputState.ChoosingCandidate {
|
) -> InputState.ChoosingCandidate {
|
||||||
InputState.ChoosingCandidate(
|
InputState.ChoosingCandidate(
|
||||||
composingBuffer: currentState.composingBuffer,
|
displayedText: currentState.displayedText,
|
||||||
cursorIndex: currentState.cursorIndex,
|
cursorIndex: currentState.cursorIndex,
|
||||||
candidates: getCandidatesArray(fixOrder: mgrPrefs.useFixecCandidateOrderOnSelection),
|
candidates: getCandidatesArray(fixOrder: mgrPrefs.useFixecCandidateOrderOnSelection),
|
||||||
isTypingVertical: isTypingVertical,
|
|
||||||
nodeValuesArray: compositor.walkedNodes.values
|
nodeValuesArray: compositor.walkedNodes.values
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -147,16 +146,13 @@ extension KeyHandler {
|
||||||
/// 是否為空:如果陣列為空的話,直接回呼一個空狀態。
|
/// 是否為空:如果陣列為空的話,直接回呼一個空狀態。
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - key: 給定的索引鍵(也就是給定的聯想詞的開頭字)。
|
/// - key: 給定的索引鍵(也就是給定的聯想詞的開頭字)。
|
||||||
/// - isTypingVertical: 是否縱排輸入?
|
|
||||||
/// - Returns: 回呼一個新的聯想詞狀態,來就給定的聯想詞陣列資料內容顯示選字窗。
|
/// - Returns: 回呼一個新的聯想詞狀態,來就給定的聯想詞陣列資料內容顯示選字窗。
|
||||||
func buildAssociatePhraseState(
|
func buildAssociatePhraseState(
|
||||||
withPair pair: Megrez.Compositor.Candidate,
|
withPair pair: Megrez.Compositor.Candidate
|
||||||
isTypingVertical: Bool
|
) -> InputState.Associates! {
|
||||||
) -> InputState.AssociatedPhrases! {
|
|
||||||
// 上一行必須要用驚嘆號,否則 Xcode 會誤導你砍掉某些實際上必需的語句。
|
// 上一行必須要用驚嘆號,否則 Xcode 會誤導你砍掉某些實際上必需的語句。
|
||||||
InputState.AssociatedPhrases(
|
InputState.Associates(
|
||||||
candidates: buildAssociatePhraseArray(withPair: pair), isTypingVertical: isTypingVertical
|
candidates: buildAssociatePhraseArray(withPair: pair))
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 用以處理就地新增自訂語彙時的行為
|
// MARK: - 用以處理就地新增自訂語彙時的行為
|
||||||
|
@ -190,7 +186,7 @@ extension KeyHandler {
|
||||||
if input.isEnter {
|
if input.isEnter {
|
||||||
if let keyHandlerDelegate = delegate {
|
if let keyHandlerDelegate = delegate {
|
||||||
// 先判斷是否是在摁了降權組合鍵的時候目標不在庫。
|
// 先判斷是否是在摁了降權組合鍵的時候目標不在庫。
|
||||||
if input.isShiftHold, input.isCommandHold, !state.validToFilter {
|
if input.isShiftHold, input.isCommandHold, !state.isFilterable {
|
||||||
IME.prtDebugIntel("2EAC1F7A")
|
IME.prtDebugIntel("2EAC1F7A")
|
||||||
errorCallback()
|
errorCallback()
|
||||||
return true
|
return true
|
||||||
|
@ -207,7 +203,7 @@ extension KeyHandler {
|
||||||
// BackSpace & Delete
|
// BackSpace & Delete
|
||||||
if input.isBackSpace || input.isDelete {
|
if input.isBackSpace || input.isDelete {
|
||||||
if let keyHandlerDelegate = delegate {
|
if let keyHandlerDelegate = delegate {
|
||||||
if !state.validToFilter {
|
if !state.isFilterable {
|
||||||
IME.prtDebugIntel("1F88B191")
|
IME.prtDebugIntel("1F88B191")
|
||||||
errorCallback()
|
errorCallback()
|
||||||
return true
|
return true
|
||||||
|
@ -226,15 +222,15 @@ extension KeyHandler {
|
||||||
if input.isCursorBackward || input.emacsKey == EmacsKey.backward, input.isShiftHold {
|
if input.isCursorBackward || input.emacsKey == EmacsKey.backward, input.isShiftHold {
|
||||||
var index = state.markerIndex
|
var index = state.markerIndex
|
||||||
if index > 0 {
|
if index > 0 {
|
||||||
index = state.composingBuffer.utf16PreviousPosition(for: index)
|
index = state.displayedText.utf16PreviousPosition(for: index)
|
||||||
let marking = InputState.Marking(
|
let marking = InputState.Marking(
|
||||||
composingBuffer: state.composingBuffer,
|
displayedText: state.displayedText,
|
||||||
cursorIndex: state.cursorIndex,
|
cursorIndex: state.cursorIndex,
|
||||||
markerIndex: index,
|
markerIndex: index,
|
||||||
readings: state.readings,
|
readings: state.readings,
|
||||||
nodeValuesArray: compositor.walkedNodes.values
|
nodeValuesArray: compositor.walkedNodes.values
|
||||||
)
|
)
|
||||||
marking.tooltipForInputting = state.tooltipForInputting
|
marking.tooltipBackupForInputting = state.tooltipBackupForInputting
|
||||||
stateCallback(marking.markedRange.isEmpty ? marking.convertedToInputting : marking)
|
stateCallback(marking.markedRange.isEmpty ? marking.convertedToInputting : marking)
|
||||||
} else {
|
} else {
|
||||||
IME.prtDebugIntel("1149908D")
|
IME.prtDebugIntel("1149908D")
|
||||||
|
@ -247,16 +243,16 @@ extension KeyHandler {
|
||||||
// Shift + Right
|
// Shift + Right
|
||||||
if input.isCursorForward || input.emacsKey == EmacsKey.forward, input.isShiftHold {
|
if input.isCursorForward || input.emacsKey == EmacsKey.forward, input.isShiftHold {
|
||||||
var index = state.markerIndex
|
var index = state.markerIndex
|
||||||
if index < (state.composingBuffer.utf16.count) {
|
if index < (state.displayedText.utf16.count) {
|
||||||
index = state.composingBuffer.utf16NextPosition(for: index)
|
index = state.displayedText.utf16NextPosition(for: index)
|
||||||
let marking = InputState.Marking(
|
let marking = InputState.Marking(
|
||||||
composingBuffer: state.composingBuffer,
|
displayedText: state.displayedText,
|
||||||
cursorIndex: state.cursorIndex,
|
cursorIndex: state.cursorIndex,
|
||||||
markerIndex: index,
|
markerIndex: index,
|
||||||
readings: state.readings,
|
readings: state.readings,
|
||||||
nodeValuesArray: compositor.walkedNodes.values
|
nodeValuesArray: compositor.walkedNodes.values
|
||||||
)
|
)
|
||||||
marking.tooltipForInputting = state.tooltipForInputting
|
marking.tooltipBackupForInputting = state.tooltipBackupForInputting
|
||||||
stateCallback(marking.markedRange.isEmpty ? marking.convertedToInputting : marking)
|
stateCallback(marking.markedRange.isEmpty ? marking.convertedToInputting : marking)
|
||||||
} else {
|
} else {
|
||||||
IME.prtDebugIntel("9B51408D")
|
IME.prtDebugIntel("9B51408D")
|
||||||
|
@ -336,7 +332,7 @@ extension KeyHandler {
|
||||||
) -> Bool {
|
) -> Bool {
|
||||||
guard let currentState = state as? InputState.Inputting else { return false }
|
guard let currentState = state as? InputState.Inputting else { return false }
|
||||||
|
|
||||||
stateCallback(InputState.Committing(textToCommit: currentState.composingBuffer))
|
stateCallback(InputState.Committing(textToCommit: currentState.displayedText))
|
||||||
stateCallback(InputState.Empty())
|
stateCallback(InputState.Empty())
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -354,17 +350,17 @@ extension KeyHandler {
|
||||||
) -> Bool {
|
) -> Bool {
|
||||||
guard state is InputState.Inputting else { return false }
|
guard state is InputState.Inputting else { return false }
|
||||||
|
|
||||||
var composingBuffer = compositor.keys.joined(separator: "-")
|
var displayedText = compositor.keys.joined(separator: "-")
|
||||||
if mgrPrefs.inlineDumpPinyinInLieuOfZhuyin {
|
if mgrPrefs.inlineDumpPinyinInLieuOfZhuyin {
|
||||||
composingBuffer = Tekkon.restoreToneOneInZhuyinKey(target: composingBuffer) // 恢復陰平標記
|
displayedText = Tekkon.restoreToneOneInZhuyinKey(target: displayedText) // 恢復陰平標記
|
||||||
composingBuffer = Tekkon.cnvPhonaToHanyuPinyin(target: composingBuffer) // 注音轉拼音
|
displayedText = Tekkon.cnvPhonaToHanyuPinyin(target: displayedText) // 注音轉拼音
|
||||||
}
|
}
|
||||||
|
|
||||||
if let delegate = delegate, !delegate.clientBundleIdentifier.contains("vChewingPhraseEditor") {
|
if let delegate = delegate, !delegate.clientBundleIdentifier.contains("vChewingPhraseEditor") {
|
||||||
composingBuffer = composingBuffer.replacingOccurrences(of: "-", with: " ")
|
displayedText = displayedText.replacingOccurrences(of: "-", with: " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
stateCallback(InputState.Committing(textToCommit: composingBuffer))
|
stateCallback(InputState.Committing(textToCommit: displayedText))
|
||||||
stateCallback(InputState.Empty())
|
stateCallback(InputState.Empty())
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -434,14 +430,14 @@ extension KeyHandler {
|
||||||
stateCallback(buildInputtingState)
|
stateCallback(buildInputtingState)
|
||||||
return true
|
return true
|
||||||
case 1:
|
case 1:
|
||||||
stateCallback(InputState.EmptyIgnoringPreviousState())
|
stateCallback(InputState.Abortion())
|
||||||
stateCallback(InputState.Empty())
|
stateCallback(InputState.Empty())
|
||||||
return true
|
return true
|
||||||
default: break
|
default: break
|
||||||
}
|
}
|
||||||
|
|
||||||
if input.isShiftHold, input.isOptionHold {
|
if input.isShiftHold, input.isOptionHold {
|
||||||
stateCallback(InputState.EmptyIgnoringPreviousState())
|
stateCallback(InputState.Abortion())
|
||||||
stateCallback(InputState.Empty())
|
stateCallback(InputState.Empty())
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -465,7 +461,7 @@ extension KeyHandler {
|
||||||
switch composer.isEmpty && compositor.isEmpty {
|
switch composer.isEmpty && compositor.isEmpty {
|
||||||
case false: stateCallback(buildInputtingState)
|
case false: stateCallback(buildInputtingState)
|
||||||
case true:
|
case true:
|
||||||
stateCallback(InputState.EmptyIgnoringPreviousState())
|
stateCallback(InputState.Abortion())
|
||||||
stateCallback(InputState.Empty())
|
stateCallback(InputState.Empty())
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
@ -489,7 +485,7 @@ extension KeyHandler {
|
||||||
guard state is InputState.Inputting else { return false }
|
guard state is InputState.Inputting else { return false }
|
||||||
|
|
||||||
if input.isShiftHold {
|
if input.isShiftHold {
|
||||||
stateCallback(InputState.EmptyIgnoringPreviousState())
|
stateCallback(InputState.Abortion())
|
||||||
stateCallback(InputState.Empty())
|
stateCallback(InputState.Empty())
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -510,10 +506,10 @@ extension KeyHandler {
|
||||||
|
|
||||||
let inputting = buildInputtingState
|
let inputting = buildInputtingState
|
||||||
// 這裡不用「count > 0」,因為該整數變數只要「!isEmpty」那就必定滿足這個條件。
|
// 這裡不用「count > 0」,因為該整數變數只要「!isEmpty」那就必定滿足這個條件。
|
||||||
switch inputting.composingBuffer.isEmpty {
|
switch inputting.displayedText.isEmpty {
|
||||||
case false: stateCallback(inputting)
|
case false: stateCallback(inputting)
|
||||||
case true:
|
case true:
|
||||||
stateCallback(InputState.EmptyIgnoringPreviousState())
|
stateCallback(InputState.Abortion())
|
||||||
stateCallback(InputState.Empty())
|
stateCallback(InputState.Empty())
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
@ -625,7 +621,7 @@ extension KeyHandler {
|
||||||
if mgrPrefs.escToCleanInputBuffer {
|
if mgrPrefs.escToCleanInputBuffer {
|
||||||
/// 若啟用了該選項,則清空組字器的內容與注拼槽的內容。
|
/// 若啟用了該選項,則清空組字器的內容與注拼槽的內容。
|
||||||
/// 此乃 macOS 內建注音輸入法預設之行為,但不太受 Windows 使用者群體之待見。
|
/// 此乃 macOS 內建注音輸入法預設之行為,但不太受 Windows 使用者群體之待見。
|
||||||
stateCallback(InputState.EmptyIgnoringPreviousState())
|
stateCallback(InputState.Abortion())
|
||||||
stateCallback(InputState.Empty())
|
stateCallback(InputState.Empty())
|
||||||
} else {
|
} else {
|
||||||
if composer.isEmpty { return true }
|
if composer.isEmpty { return true }
|
||||||
|
@ -634,7 +630,7 @@ extension KeyHandler {
|
||||||
switch compositor.isEmpty {
|
switch compositor.isEmpty {
|
||||||
case false: stateCallback(buildInputtingState)
|
case false: stateCallback(buildInputtingState)
|
||||||
case true:
|
case true:
|
||||||
stateCallback(InputState.EmptyIgnoringPreviousState())
|
stateCallback(InputState.Abortion())
|
||||||
stateCallback(InputState.Empty())
|
stateCallback(InputState.Empty())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -667,16 +663,16 @@ extension KeyHandler {
|
||||||
|
|
||||||
if input.isShiftHold {
|
if input.isShiftHold {
|
||||||
// Shift + Right
|
// Shift + Right
|
||||||
if currentState.cursorIndex < currentState.composingBuffer.utf16.count {
|
if currentState.cursorIndex < currentState.displayedText.utf16.count {
|
||||||
let nextPosition = currentState.composingBuffer.utf16NextPosition(
|
let nextPosition = currentState.displayedText.utf16NextPosition(
|
||||||
for: currentState.cursorIndex)
|
for: currentState.cursorIndex)
|
||||||
let marking: InputState.Marking! = InputState.Marking(
|
let marking: InputState.Marking! = InputState.Marking(
|
||||||
composingBuffer: currentState.composingBuffer,
|
displayedText: currentState.displayedText,
|
||||||
cursorIndex: currentState.cursorIndex,
|
cursorIndex: currentState.cursorIndex,
|
||||||
markerIndex: nextPosition,
|
markerIndex: nextPosition,
|
||||||
readings: compositor.keys
|
readings: compositor.keys
|
||||||
)
|
)
|
||||||
marking.tooltipForInputting = currentState.tooltip
|
marking.tooltipBackupForInputting = currentState.tooltip
|
||||||
stateCallback(marking)
|
stateCallback(marking)
|
||||||
} else {
|
} else {
|
||||||
IME.prtDebugIntel("BB7F6DB9")
|
IME.prtDebugIntel("BB7F6DB9")
|
||||||
|
@ -742,15 +738,15 @@ extension KeyHandler {
|
||||||
if input.isShiftHold {
|
if input.isShiftHold {
|
||||||
// Shift + left
|
// Shift + left
|
||||||
if currentState.cursorIndex > 0 {
|
if currentState.cursorIndex > 0 {
|
||||||
let previousPosition = currentState.composingBuffer.utf16PreviousPosition(
|
let previousPosition = currentState.displayedText.utf16PreviousPosition(
|
||||||
for: currentState.cursorIndex)
|
for: currentState.cursorIndex)
|
||||||
let marking: InputState.Marking! = InputState.Marking(
|
let marking: InputState.Marking! = InputState.Marking(
|
||||||
composingBuffer: currentState.composingBuffer,
|
displayedText: currentState.displayedText,
|
||||||
cursorIndex: currentState.cursorIndex,
|
cursorIndex: currentState.cursorIndex,
|
||||||
markerIndex: previousPosition,
|
markerIndex: previousPosition,
|
||||||
readings: compositor.keys
|
readings: compositor.keys
|
||||||
)
|
)
|
||||||
marking.tooltipForInputting = currentState.tooltip
|
marking.tooltipBackupForInputting = currentState.tooltip
|
||||||
stateCallback(marking)
|
stateCallback(marking)
|
||||||
} else {
|
} else {
|
||||||
IME.prtDebugIntel("D326DEA3")
|
IME.prtDebugIntel("D326DEA3")
|
||||||
|
|
|
@ -31,11 +31,9 @@ extension ctlInputMethod {
|
||||||
if !shouldUseHandle || (!rencentKeyHandledByKeyHandler && shouldUseHandle) {
|
if !shouldUseHandle || (!rencentKeyHandledByKeyHandler && shouldUseHandle) {
|
||||||
NotifierController.notify(
|
NotifierController.notify(
|
||||||
message: NSLocalizedString("Alphanumerical Mode", comment: "") + "\n"
|
message: NSLocalizedString("Alphanumerical Mode", comment: "") + "\n"
|
||||||
+ {
|
+ toggleASCIIMode()
|
||||||
toggleASCIIMode()
|
? NSLocalizedString("NotificationSwitchON", comment: "")
|
||||||
? NSLocalizedString("NotificationSwitchON", comment: "")
|
: NSLocalizedString("NotificationSwitchOFF", comment: "")
|
||||||
: NSLocalizedString("NotificationSwitchOFF", comment: "")
|
|
||||||
}()
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if shouldUseHandle {
|
if shouldUseHandle {
|
||||||
|
|
|
@ -71,7 +71,7 @@ class ctlInputMethod: IMKInputController {
|
||||||
}
|
}
|
||||||
if let state = state as? InputState.NotEmpty {
|
if let state = state as? InputState.NotEmpty {
|
||||||
/// 將傳回的新狀態交給調度函式。
|
/// 將傳回的新狀態交給調度函式。
|
||||||
handle(state: InputState.Committing(textToCommit: state.composingBufferConverted))
|
handle(state: InputState.Committing(textToCommit: state.displayedTextConverted))
|
||||||
}
|
}
|
||||||
handle(state: InputState.Empty())
|
handle(state: InputState.Empty())
|
||||||
}
|
}
|
||||||
|
@ -115,11 +115,9 @@ class ctlInputMethod: IMKInputController {
|
||||||
} else {
|
} else {
|
||||||
NotifierController.notify(
|
NotifierController.notify(
|
||||||
message: NSLocalizedString("Alphanumerical Mode", comment: "") + "\n"
|
message: NSLocalizedString("Alphanumerical Mode", comment: "") + "\n"
|
||||||
+ {
|
+ isASCIIMode
|
||||||
isASCIIMode
|
? NSLocalizedString("NotificationSwitchON", comment: "")
|
||||||
? NSLocalizedString("NotificationSwitchON", comment: "")
|
: NSLocalizedString("NotificationSwitchOFF", comment: "")
|
||||||
: NSLocalizedString("NotificationSwitchOFF", comment: "")
|
|
||||||
}()
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -325,7 +323,7 @@ class ctlInputMethod: IMKInputController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let state = state as? InputState.AssociatedPhrases {
|
if let state = state as? InputState.Associates {
|
||||||
handleCandidatesPrepared(state.candidates, prefix: "⇧")
|
handleCandidatesPrepared(state.candidates, prefix: "⇧")
|
||||||
} else if let state = state as? InputState.SymbolTable {
|
} else if let state = state as? InputState.SymbolTable {
|
||||||
// 分類符號選單不會出現同符異音項、不需要康熙 / JIS 轉換,所以使用簡化過的處理方式。
|
// 分類符號選單不會出現同符異音項、不需要康熙 / JIS 轉換,所以使用簡化過的處理方式。
|
||||||
|
@ -363,9 +361,9 @@ class ctlInputMethod: IMKInputController {
|
||||||
/// - Parameter candidateString: 已經確認的候選字詞內容。
|
/// - Parameter candidateString: 已經確認的候選字詞內容。
|
||||||
override open func candidateSelected(_ candidateString: NSAttributedString!) {
|
override open func candidateSelected(_ candidateString: NSAttributedString!) {
|
||||||
let candidateString: NSAttributedString = candidateString ?? .init(string: "")
|
let candidateString: NSAttributedString = candidateString ?? .init(string: "")
|
||||||
if state is InputState.AssociatedPhrases {
|
if state is InputState.Associates {
|
||||||
if !mgrPrefs.alsoConfirmAssociatedCandidatesByEnter {
|
if !mgrPrefs.alsoConfirmAssociatedCandidatesByEnter {
|
||||||
handle(state: InputState.EmptyIgnoringPreviousState())
|
handle(state: InputState.Abortion())
|
||||||
handle(state: InputState.Empty())
|
handle(state: InputState.Empty())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -403,7 +401,7 @@ class ctlInputMethod: IMKInputController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let state = state as? InputState.AssociatedPhrases {
|
if let state = state as? InputState.Associates {
|
||||||
handleCandidatesSelected(state.candidates, prefix: "⇧")
|
handleCandidatesSelected(state.candidates, prefix: "⇧")
|
||||||
} else if let state = state as? InputState.SymbolTable {
|
} else if let state = state as? InputState.SymbolTable {
|
||||||
handleSymbolCandidatesSelected(state.candidates)
|
handleSymbolCandidatesSelected(state.candidates)
|
||||||
|
|
|
@ -65,7 +65,7 @@ extension ctlInputMethod: KeyHandlerDelegate {
|
||||||
// MARK: - Candidate Controller Delegate
|
// MARK: - Candidate Controller Delegate
|
||||||
|
|
||||||
extension ctlInputMethod: ctlCandidateDelegate {
|
extension ctlInputMethod: ctlCandidateDelegate {
|
||||||
var isAssociatedPhrasesState: Bool { state is InputState.AssociatedPhrases }
|
var isAssociatedPhrasesState: Bool { state is InputState.Associates }
|
||||||
|
|
||||||
/// 完成 handle() 函式本該完成的內容,但去掉了與 IMK 選字窗有關的判斷語句。
|
/// 完成 handle() 函式本該完成的內容,但去掉了與 IMK 選字窗有關的判斷語句。
|
||||||
/// 這樣分開處理很有必要,不然 handle() 函式會陷入無限迴圈。
|
/// 這樣分開處理很有必要,不然 handle() 函式會陷入無限迴圈。
|
||||||
|
@ -80,7 +80,7 @@ extension ctlInputMethod: ctlCandidateDelegate {
|
||||||
_ = controller // 防止格式整理工具毀掉與此對應的參數。
|
_ = controller // 防止格式整理工具毀掉與此對應的參數。
|
||||||
if let state = state as? InputState.ChoosingCandidate {
|
if let state = state as? InputState.ChoosingCandidate {
|
||||||
return state.candidates.count
|
return state.candidates.count
|
||||||
} else if let state = state as? InputState.AssociatedPhrases {
|
} else if let state = state as? InputState.Associates {
|
||||||
return state.candidates.count
|
return state.candidates.count
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
|
@ -93,7 +93,7 @@ extension ctlInputMethod: ctlCandidateDelegate {
|
||||||
_ = controller // 防止格式整理工具毀掉與此對應的參數。
|
_ = controller // 防止格式整理工具毀掉與此對應的參數。
|
||||||
if let state = state as? InputState.ChoosingCandidate {
|
if let state = state as? InputState.ChoosingCandidate {
|
||||||
return state.candidates
|
return state.candidates
|
||||||
} else if let state = state as? InputState.AssociatedPhrases {
|
} else if let state = state as? InputState.Associates {
|
||||||
return state.candidates
|
return state.candidates
|
||||||
}
|
}
|
||||||
return .init()
|
return .init()
|
||||||
|
@ -105,7 +105,7 @@ extension ctlInputMethod: ctlCandidateDelegate {
|
||||||
_ = controller // 防止格式整理工具毀掉與此對應的參數。
|
_ = controller // 防止格式整理工具毀掉與此對應的參數。
|
||||||
if let state = state as? InputState.ChoosingCandidate {
|
if let state = state as? InputState.ChoosingCandidate {
|
||||||
return state.candidates[index]
|
return state.candidates[index]
|
||||||
} else if let state = state as? InputState.AssociatedPhrases {
|
} else if let state = state as? InputState.Associates {
|
||||||
return state.candidates[index]
|
return state.candidates[index]
|
||||||
}
|
}
|
||||||
return ("", "")
|
return ("", "")
|
||||||
|
@ -119,9 +119,7 @@ extension ctlInputMethod: ctlCandidateDelegate {
|
||||||
{
|
{
|
||||||
if let children = node.children, !children.isEmpty {
|
if let children = node.children, !children.isEmpty {
|
||||||
handle(state: InputState.Empty()) // 防止縱橫排選字窗同時出現
|
handle(state: InputState.Empty()) // 防止縱橫排選字窗同時出現
|
||||||
handle(
|
handle(state: InputState.SymbolTable(node: node, previous: state.node))
|
||||||
state: InputState.SymbolTable(node: node, previous: state.node, isTypingVertical: state.isTypingVertical)
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
handle(state: InputState.Committing(textToCommit: node.title))
|
handle(state: InputState.Committing(textToCommit: node.title))
|
||||||
handle(state: InputState.Empty())
|
handle(state: InputState.Empty())
|
||||||
|
@ -139,12 +137,11 @@ extension ctlInputMethod: ctlCandidateDelegate {
|
||||||
let inputting = keyHandler.buildInputtingState
|
let inputting = keyHandler.buildInputtingState
|
||||||
|
|
||||||
if mgrPrefs.useSCPCTypingMode {
|
if mgrPrefs.useSCPCTypingMode {
|
||||||
handle(state: InputState.Committing(textToCommit: inputting.composingBufferConverted))
|
handle(state: InputState.Committing(textToCommit: inputting.displayedTextConverted))
|
||||||
// 此時是逐字選字模式,所以「selectedValue.1」是單個字、不用追加處理。
|
// 此時是逐字選字模式,所以「selectedValue.1」是單個字、不用追加處理。
|
||||||
if mgrPrefs.associatedPhrasesEnabled,
|
if mgrPrefs.associatedPhrasesEnabled,
|
||||||
let associatePhrases = keyHandler.buildAssociatePhraseState(
|
let associatePhrases = keyHandler.buildAssociatePhraseState(
|
||||||
withPair: .init(key: selectedValue.0, value: selectedValue.1),
|
withPair: .init(key: selectedValue.0, value: selectedValue.1)
|
||||||
isTypingVertical: state.isTypingVertical
|
|
||||||
), !associatePhrases.candidates.isEmpty
|
), !associatePhrases.candidates.isEmpty
|
||||||
{
|
{
|
||||||
handle(state: associatePhrases)
|
handle(state: associatePhrases)
|
||||||
|
@ -157,7 +154,7 @@ extension ctlInputMethod: ctlCandidateDelegate {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if let state = state as? InputState.AssociatedPhrases {
|
if let state = state as? InputState.Associates {
|
||||||
let selectedValue = state.candidates[index]
|
let selectedValue = state.candidates[index]
|
||||||
handle(state: InputState.Committing(textToCommit: selectedValue.1))
|
handle(state: InputState.Committing(textToCommit: selectedValue.1))
|
||||||
// 此時是聯想詞選字模式,所以「selectedValue.1」必須只保留最後一個字。
|
// 此時是聯想詞選字模式,所以「selectedValue.1」必須只保留最後一個字。
|
||||||
|
@ -168,8 +165,7 @@ extension ctlInputMethod: ctlCandidateDelegate {
|
||||||
}
|
}
|
||||||
if mgrPrefs.associatedPhrasesEnabled,
|
if mgrPrefs.associatedPhrasesEnabled,
|
||||||
let associatePhrases = keyHandler.buildAssociatePhraseState(
|
let associatePhrases = keyHandler.buildAssociatePhraseState(
|
||||||
withPair: .init(key: selectedValue.0, value: String(valueKept)),
|
withPair: .init(key: selectedValue.0, value: String(valueKept))
|
||||||
isTypingVertical: state.isTypingVertical
|
|
||||||
), !associatePhrases.candidates.isEmpty
|
), !associatePhrases.candidates.isEmpty
|
||||||
{
|
{
|
||||||
handle(state: associatePhrases)
|
handle(state: associatePhrases)
|
||||||
|
|
|
@ -13,11 +13,11 @@ import Cocoa
|
||||||
// MARK: - Tooltip Display and Candidate Display Methods
|
// MARK: - Tooltip Display and Candidate Display Methods
|
||||||
|
|
||||||
extension ctlInputMethod {
|
extension ctlInputMethod {
|
||||||
func show(tooltip: String, composingBuffer: String, cursorIndex: Int) {
|
func show(tooltip: String, displayedText: String, cursorIndex: Int) {
|
||||||
guard let client = client() else { return }
|
guard let client = client() else { return }
|
||||||
var lineHeightRect = NSRect(x: 0.0, y: 0.0, width: 16.0, height: 16.0)
|
var lineHeightRect = NSRect(x: 0.0, y: 0.0, width: 16.0, height: 16.0)
|
||||||
var cursor = cursorIndex
|
var cursor = cursorIndex
|
||||||
if cursor == composingBuffer.count, cursor != 0 {
|
if cursor == displayedText.count, cursor != 0 {
|
||||||
cursor -= 1
|
cursor -= 1
|
||||||
}
|
}
|
||||||
while lineHeightRect.origin.x == 0, lineHeightRect.origin.y == 0, cursor >= 0 {
|
while lineHeightRect.origin.x == 0, lineHeightRect.origin.y == 0, cursor >= 0 {
|
||||||
|
@ -41,10 +41,10 @@ extension ctlInputMethod {
|
||||||
func show(candidateWindowWith state: InputStateProtocol) {
|
func show(candidateWindowWith state: InputStateProtocol) {
|
||||||
guard let client = client() else { return }
|
guard let client = client() else { return }
|
||||||
var isTypingVertical: Bool {
|
var isTypingVertical: Bool {
|
||||||
if let state = state as? InputState.ChoosingCandidate {
|
if state.type == .ofCandidates {
|
||||||
return state.isTypingVertical
|
return ctlInputMethod.isVerticalTypingSituation
|
||||||
} else if let state = state as? InputState.AssociatedPhrases {
|
} else if state.type == ..ofAssociates {
|
||||||
return state.isTypingVertical
|
return ctlInputMethod.isVerticalTypingSituation
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,7 @@ extension ctlInputMethod {
|
||||||
var candidates: [(String, String)] = .init()
|
var candidates: [(String, String)] = .init()
|
||||||
if let state = state as? InputState.ChoosingCandidate {
|
if let state = state as? InputState.ChoosingCandidate {
|
||||||
candidates = state.candidates
|
candidates = state.candidates
|
||||||
} else if let state = state as? InputState.AssociatedPhrases {
|
} else if let state = state as? InputState.Associates {
|
||||||
candidates = state.candidates
|
candidates = state.candidates
|
||||||
}
|
}
|
||||||
if isTypingVertical { return true }
|
if isTypingVertical { return true }
|
||||||
|
@ -106,7 +106,7 @@ extension ctlInputMethod {
|
||||||
let candidateKeys = mgrPrefs.candidateKeys
|
let candidateKeys = mgrPrefs.candidateKeys
|
||||||
let keyLabels =
|
let keyLabels =
|
||||||
candidateKeys.count > 4 ? Array(candidateKeys) : Array(mgrPrefs.defaultCandidateKeys)
|
candidateKeys.count > 4 ? Array(candidateKeys) : Array(mgrPrefs.defaultCandidateKeys)
|
||||||
let keyLabelSuffix = state is InputState.AssociatedPhrases ? "^" : ""
|
let keyLabelSuffix = state is InputState.Associates ? "^" : ""
|
||||||
ctlInputMethod.ctlCandidateCurrent.keyLabels = keyLabels.map {
|
ctlInputMethod.ctlCandidateCurrent.keyLabels = keyLabels.map {
|
||||||
CandidateKeyLabel(key: String($0), displayedText: String($0) + keyLabelSuffix)
|
CandidateKeyLabel(key: String($0), displayedText: String($0) + keyLabelSuffix)
|
||||||
}
|
}
|
||||||
|
@ -128,7 +128,7 @@ extension ctlInputMethod {
|
||||||
|
|
||||||
if let state = state as? InputState.ChoosingCandidate {
|
if let state = state as? InputState.ChoosingCandidate {
|
||||||
cursor = state.cursorIndex
|
cursor = state.cursorIndex
|
||||||
if cursor == state.composingBuffer.count, cursor != 0 {
|
if cursor == state.displayedText.count, cursor != 0 {
|
||||||
cursor -= 1
|
cursor -= 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ extension ctlInputMethod {
|
||||||
handle(state: newState, previous: prevState)
|
handle(state: newState, previous: prevState)
|
||||||
case let newState as InputState.Empty:
|
case let newState as InputState.Empty:
|
||||||
handle(state: newState, previous: prevState)
|
handle(state: newState, previous: prevState)
|
||||||
case let newState as InputState.EmptyIgnoringPreviousState:
|
case let newState as InputState.Abortion:
|
||||||
handle(state: newState, previous: prevState)
|
handle(state: newState, previous: prevState)
|
||||||
case let newState as InputState.Committing:
|
case let newState as InputState.Committing:
|
||||||
handle(state: newState, previous: prevState)
|
handle(state: newState, previous: prevState)
|
||||||
|
@ -37,7 +37,7 @@ extension ctlInputMethod {
|
||||||
handle(state: newState, previous: prevState)
|
handle(state: newState, previous: prevState)
|
||||||
case let newState as InputState.ChoosingCandidate:
|
case let newState as InputState.ChoosingCandidate:
|
||||||
handle(state: newState, previous: prevState)
|
handle(state: newState, previous: prevState)
|
||||||
case let newState as InputState.AssociatedPhrases:
|
case let newState as InputState.Associates:
|
||||||
handle(state: newState, previous: prevState)
|
handle(state: newState, previous: prevState)
|
||||||
case let newState as InputState.SymbolTable:
|
case let newState as InputState.SymbolTable:
|
||||||
handle(state: newState, previous: prevState)
|
handle(state: newState, previous: prevState)
|
||||||
|
@ -48,7 +48,7 @@ extension ctlInputMethod {
|
||||||
/// 針對受 .NotEmpty() 管轄的非空狀態,在組字區內顯示游標。
|
/// 針對受 .NotEmpty() 管轄的非空狀態,在組字區內顯示游標。
|
||||||
func setInlineDisplayWithCursor() {
|
func setInlineDisplayWithCursor() {
|
||||||
guard let client = client() else { return }
|
guard let client = client() else { return }
|
||||||
if let state = state as? InputState.AssociatedPhrases {
|
if let state = state as? InputState.Associates {
|
||||||
client.setMarkedText(
|
client.setMarkedText(
|
||||||
state.attributedString, selectionRange: NSRange(location: 0, length: 0),
|
state.attributedString, selectionRange: NSRange(location: 0, length: 0),
|
||||||
replacementRange: NSRange(location: NSNotFound, length: NSNotFound)
|
replacementRange: NSRange(location: NSNotFound, length: NSNotFound)
|
||||||
|
@ -87,7 +87,7 @@ extension ctlInputMethod {
|
||||||
[.languageIdentifier: identifier],
|
[.languageIdentifier: identifier],
|
||||||
range: NSRange(
|
range: NSRange(
|
||||||
location: 0,
|
location: 0,
|
||||||
length: state.composingBuffer.utf16.count
|
length: state.displayedText.utf16.count
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -141,9 +141,9 @@ extension ctlInputMethod {
|
||||||
_ = state // 防止格式整理工具毀掉與此對應的參數。
|
_ = state // 防止格式整理工具毀掉與此對應的參數。
|
||||||
ctlInputMethod.ctlCandidateCurrent.visible = false
|
ctlInputMethod.ctlCandidateCurrent.visible = false
|
||||||
ctlInputMethod.tooltipController.hide()
|
ctlInputMethod.tooltipController.hide()
|
||||||
// 全專案用以判斷「.EmptyIgnoringPreviousState」的地方僅此一處。
|
// 全專案用以判斷「.Abortion」的地方僅此一處。
|
||||||
if let previous = previous as? InputState.NotEmpty,
|
if let previous = previous as? InputState.NotEmpty,
|
||||||
!(state is InputState.EmptyIgnoringPreviousState)
|
!(state is InputState.Abortion)
|
||||||
{
|
{
|
||||||
commit(text: previous.committingBufferConverted)
|
commit(text: previous.committingBufferConverted)
|
||||||
}
|
}
|
||||||
|
@ -156,7 +156,7 @@ extension ctlInputMethod {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handle(
|
private func handle(
|
||||||
state: InputState.EmptyIgnoringPreviousState, previous: InputStateProtocol
|
state: InputState.Abortion, previous: InputStateProtocol
|
||||||
) {
|
) {
|
||||||
_ = state // 防止格式整理工具毀掉與此對應的參數。
|
_ = state // 防止格式整理工具毀掉與此對應的參數。
|
||||||
_ = previous // 防止格式整理工具毀掉與此對應的參數。
|
_ = previous // 防止格式整理工具毀掉與此對應的參數。
|
||||||
|
@ -188,7 +188,7 @@ extension ctlInputMethod {
|
||||||
setInlineDisplayWithCursor()
|
setInlineDisplayWithCursor()
|
||||||
if !state.tooltip.isEmpty {
|
if !state.tooltip.isEmpty {
|
||||||
show(
|
show(
|
||||||
tooltip: state.tooltip, composingBuffer: state.composingBuffer,
|
tooltip: state.tooltip, displayedText: state.displayedText,
|
||||||
cursorIndex: state.cursorIndex
|
cursorIndex: state.cursorIndex
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -202,7 +202,7 @@ extension ctlInputMethod {
|
||||||
ctlInputMethod.tooltipController.hide()
|
ctlInputMethod.tooltipController.hide()
|
||||||
} else {
|
} else {
|
||||||
show(
|
show(
|
||||||
tooltip: state.tooltip, composingBuffer: state.composingBuffer,
|
tooltip: state.tooltip, displayedText: state.displayedText,
|
||||||
cursorIndex: state.markerIndex
|
cursorIndex: state.markerIndex
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -222,7 +222,7 @@ extension ctlInputMethod {
|
||||||
show(candidateWindowWith: state)
|
show(candidateWindowWith: state)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handle(state: InputState.AssociatedPhrases, previous: InputStateProtocol) {
|
private func handle(state: InputState.Associates, previous: InputStateProtocol) {
|
||||||
_ = previous // 防止格式整理工具毀掉與此對應的參數。
|
_ = previous // 防止格式整理工具毀掉與此對應的參數。
|
||||||
ctlInputMethod.tooltipController.hide()
|
ctlInputMethod.tooltipController.hide()
|
||||||
setInlineDisplayWithCursor()
|
setInlineDisplayWithCursor()
|
||||||
|
|
|
@ -212,11 +212,9 @@ extension ctlInputMethod {
|
||||||
resetKeyHandler()
|
resetKeyHandler()
|
||||||
NotifierController.notify(
|
NotifierController.notify(
|
||||||
message: NSLocalizedString("Per-Char Select Mode", comment: "") + "\n"
|
message: NSLocalizedString("Per-Char Select Mode", comment: "") + "\n"
|
||||||
+ {
|
+ mgrPrefs.toggleSCPCTypingModeEnabled()
|
||||||
mgrPrefs.toggleSCPCTypingModeEnabled()
|
? NSLocalizedString("NotificationSwitchON", comment: "")
|
||||||
? NSLocalizedString("NotificationSwitchON", comment: "")
|
: NSLocalizedString("NotificationSwitchOFF", comment: "")
|
||||||
: NSLocalizedString("NotificationSwitchOFF", comment: "")
|
|
||||||
}()
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,11 +222,9 @@ extension ctlInputMethod {
|
||||||
resetKeyHandler()
|
resetKeyHandler()
|
||||||
NotifierController.notify(
|
NotifierController.notify(
|
||||||
message: NSLocalizedString("Force KangXi Writing", comment: "") + "\n"
|
message: NSLocalizedString("Force KangXi Writing", comment: "") + "\n"
|
||||||
+ {
|
+ mgrPrefs.toggleChineseConversionEnabled()
|
||||||
mgrPrefs.toggleChineseConversionEnabled()
|
? NSLocalizedString("NotificationSwitchON", comment: "")
|
||||||
? NSLocalizedString("NotificationSwitchON", comment: "")
|
: NSLocalizedString("NotificationSwitchOFF", comment: "")
|
||||||
: NSLocalizedString("NotificationSwitchOFF", comment: "")
|
|
||||||
}()
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -236,11 +232,9 @@ extension ctlInputMethod {
|
||||||
resetKeyHandler()
|
resetKeyHandler()
|
||||||
NotifierController.notify(
|
NotifierController.notify(
|
||||||
message: NSLocalizedString("JIS Shinjitai Output", comment: "") + "\n"
|
message: NSLocalizedString("JIS Shinjitai Output", comment: "") + "\n"
|
||||||
+ {
|
+ mgrPrefs.toggleShiftJISShinjitaiOutputEnabled()
|
||||||
mgrPrefs.toggleShiftJISShinjitaiOutputEnabled()
|
? NSLocalizedString("NotificationSwitchON", comment: "")
|
||||||
? NSLocalizedString("NotificationSwitchON", comment: "")
|
: NSLocalizedString("NotificationSwitchOFF", comment: "")
|
||||||
: NSLocalizedString("NotificationSwitchOFF", comment: "")
|
|
||||||
}()
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -248,11 +242,9 @@ extension ctlInputMethod {
|
||||||
resetKeyHandler()
|
resetKeyHandler()
|
||||||
NotifierController.notify(
|
NotifierController.notify(
|
||||||
message: NSLocalizedString("Currency Numeral Output", comment: "") + "\n"
|
message: NSLocalizedString("Currency Numeral Output", comment: "") + "\n"
|
||||||
+ {
|
+ mgrPrefs.toggleCurrencyNumeralsEnabled()
|
||||||
mgrPrefs.toggleCurrencyNumeralsEnabled()
|
? NSLocalizedString("NotificationSwitchON", comment: "")
|
||||||
? NSLocalizedString("NotificationSwitchON", comment: "")
|
: NSLocalizedString("NotificationSwitchOFF", comment: "")
|
||||||
: NSLocalizedString("NotificationSwitchOFF", comment: "")
|
|
||||||
}()
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -260,11 +252,9 @@ extension ctlInputMethod {
|
||||||
resetKeyHandler()
|
resetKeyHandler()
|
||||||
NotifierController.notify(
|
NotifierController.notify(
|
||||||
message: NSLocalizedString("Half-Width Punctuation Mode", comment: "") + "\n"
|
message: NSLocalizedString("Half-Width Punctuation Mode", comment: "") + "\n"
|
||||||
+ {
|
+ mgrPrefs.toggleHalfWidthPunctuationEnabled()
|
||||||
mgrPrefs.toggleHalfWidthPunctuationEnabled()
|
? NSLocalizedString("NotificationSwitchON", comment: "")
|
||||||
? NSLocalizedString("NotificationSwitchON", comment: "")
|
: NSLocalizedString("NotificationSwitchOFF", comment: "")
|
||||||
: NSLocalizedString("NotificationSwitchOFF", comment: "")
|
|
||||||
}()
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -272,11 +262,9 @@ extension ctlInputMethod {
|
||||||
resetKeyHandler()
|
resetKeyHandler()
|
||||||
NotifierController.notify(
|
NotifierController.notify(
|
||||||
message: NSLocalizedString("CNS11643 Mode", comment: "") + "\n"
|
message: NSLocalizedString("CNS11643 Mode", comment: "") + "\n"
|
||||||
+ {
|
+ mgrPrefs.toggleCNS11643Enabled()
|
||||||
mgrPrefs.toggleCNS11643Enabled()
|
? NSLocalizedString("NotificationSwitchON", comment: "")
|
||||||
? NSLocalizedString("NotificationSwitchON", comment: "")
|
: NSLocalizedString("NotificationSwitchOFF", comment: "")
|
||||||
: NSLocalizedString("NotificationSwitchOFF", comment: "")
|
|
||||||
}()
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -284,11 +272,9 @@ extension ctlInputMethod {
|
||||||
resetKeyHandler()
|
resetKeyHandler()
|
||||||
NotifierController.notify(
|
NotifierController.notify(
|
||||||
message: NSLocalizedString("Symbol & Emoji Input", comment: "") + "\n"
|
message: NSLocalizedString("Symbol & Emoji Input", comment: "") + "\n"
|
||||||
+ {
|
+ mgrPrefs.toggleSymbolInputEnabled()
|
||||||
mgrPrefs.toggleSymbolInputEnabled()
|
? NSLocalizedString("NotificationSwitchON", comment: "")
|
||||||
? NSLocalizedString("NotificationSwitchON", comment: "")
|
: NSLocalizedString("NotificationSwitchOFF", comment: "")
|
||||||
: NSLocalizedString("NotificationSwitchOFF", comment: "")
|
|
||||||
}()
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -296,11 +282,9 @@ extension ctlInputMethod {
|
||||||
resetKeyHandler()
|
resetKeyHandler()
|
||||||
NotifierController.notify(
|
NotifierController.notify(
|
||||||
message: NSLocalizedString("Per-Char Associated Phrases", comment: "") + "\n"
|
message: NSLocalizedString("Per-Char Associated Phrases", comment: "") + "\n"
|
||||||
+ {
|
+ mgrPrefs.toggleAssociatedPhrasesEnabled()
|
||||||
mgrPrefs.toggleAssociatedPhrasesEnabled()
|
? NSLocalizedString("NotificationSwitchON", comment: "")
|
||||||
? NSLocalizedString("NotificationSwitchON", comment: "")
|
: NSLocalizedString("NotificationSwitchOFF", comment: "")
|
||||||
: NSLocalizedString("NotificationSwitchOFF", comment: "")
|
|
||||||
}()
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -308,11 +292,9 @@ extension ctlInputMethod {
|
||||||
resetKeyHandler()
|
resetKeyHandler()
|
||||||
NotifierController.notify(
|
NotifierController.notify(
|
||||||
message: NSLocalizedString("Use Phrase Replacement", comment: "") + "\n"
|
message: NSLocalizedString("Use Phrase Replacement", comment: "") + "\n"
|
||||||
+ {
|
+ mgrPrefs.togglePhraseReplacementEnabled()
|
||||||
mgrPrefs.togglePhraseReplacementEnabled()
|
? NSLocalizedString("NotificationSwitchON", comment: "")
|
||||||
? NSLocalizedString("NotificationSwitchON", comment: "")
|
: NSLocalizedString("NotificationSwitchOFF", comment: "")
|
||||||
: NSLocalizedString("NotificationSwitchOFF", comment: "")
|
|
||||||
}()
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -438,6 +438,7 @@ public enum mgrPrefs {
|
||||||
|
|
||||||
@UserDefault(key: UserDef.kTrimUnfinishedReadingsOnCommit.rawValue, defaultValue: true)
|
@UserDefault(key: UserDef.kTrimUnfinishedReadingsOnCommit.rawValue, defaultValue: true)
|
||||||
static var trimUnfinishedReadingsOnCommit: Bool
|
static var trimUnfinishedReadingsOnCommit: Bool
|
||||||
|
|
||||||
// MARK: - Settings (Tier 2)
|
// MARK: - Settings (Tier 2)
|
||||||
|
|
||||||
@UserDefault(key: UserDef.kUseIMKCandidateWindow.rawValue, defaultValue: false)
|
@UserDefault(key: UserDef.kUseIMKCandidateWindow.rawValue, defaultValue: false)
|
||||||
|
@ -458,6 +459,8 @@ public enum mgrPrefs {
|
||||||
@UserDefault(key: UserDef.kMaxCandidateLength.rawValue, defaultValue: 10)
|
@UserDefault(key: UserDef.kMaxCandidateLength.rawValue, defaultValue: 10)
|
||||||
static var maxCandidateLength: Int
|
static var maxCandidateLength: Int
|
||||||
|
|
||||||
|
static var allowedMarkRange: ClosedRange<Int> = mgrPrefs.minCandidateLength...mgrPrefs.maxCandidateLength
|
||||||
|
|
||||||
@UserDefault(key: UserDef.kShouldNotFartInLieuOfBeep.rawValue, defaultValue: true)
|
@UserDefault(key: UserDef.kShouldNotFartInLieuOfBeep.rawValue, defaultValue: true)
|
||||||
static var shouldNotFartInLieuOfBeep: Bool
|
static var shouldNotFartInLieuOfBeep: Bool
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class SymbolNode {
|
public class SymbolNode {
|
||||||
var title: String
|
var title: String
|
||||||
var children: [SymbolNode]?
|
var children: [SymbolNode]?
|
||||||
var previous: SymbolNode?
|
var previous: SymbolNode?
|
||||||
|
|
|
@ -117,6 +117,8 @@
|
||||||
5BEDB724283B4C250078EB25 /* data-symbols.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5BEDB71E283B4AEA0078EB25 /* data-symbols.plist */; };
|
5BEDB724283B4C250078EB25 /* data-symbols.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5BEDB71E283B4AEA0078EB25 /* data-symbols.plist */; };
|
||||||
5BEDB725283B4C250078EB25 /* data-chs.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5BEDB71C283B4AEA0078EB25 /* data-chs.plist */; };
|
5BEDB725283B4C250078EB25 /* data-chs.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5BEDB71C283B4AEA0078EB25 /* data-chs.plist */; };
|
||||||
5BF0B84C28C070B000795FC6 /* NSEventExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BF0B84B28C070B000795FC6 /* NSEventExtension.swift */; };
|
5BF0B84C28C070B000795FC6 /* NSEventExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BF0B84B28C070B000795FC6 /* NSEventExtension.swift */; };
|
||||||
|
5BF56F9828C39A2700DD6839 /* IMEState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BF56F9728C39A2700DD6839 /* IMEState.swift */; };
|
||||||
|
5BF56F9A28C39D1800DD6839 /* IMEStateData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BF56F9928C39D1800DD6839 /* IMEStateData.swift */; };
|
||||||
5BF9DA2728840E6200DBD48E /* template-usersymbolphrases.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5BF9DA2228840E6200DBD48E /* template-usersymbolphrases.txt */; };
|
5BF9DA2728840E6200DBD48E /* template-usersymbolphrases.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5BF9DA2228840E6200DBD48E /* template-usersymbolphrases.txt */; };
|
||||||
5BF9DA2828840E6200DBD48E /* template-exclusions.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5BF9DA2328840E6200DBD48E /* template-exclusions.txt */; };
|
5BF9DA2828840E6200DBD48E /* template-exclusions.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5BF9DA2328840E6200DBD48E /* template-exclusions.txt */; };
|
||||||
5BF9DA2928840E6200DBD48E /* template-associatedPhrases-chs.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5BF9DA2428840E6200DBD48E /* template-associatedPhrases-chs.txt */; };
|
5BF9DA2928840E6200DBD48E /* template-associatedPhrases-chs.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5BF9DA2428840E6200DBD48E /* template-associatedPhrases-chs.txt */; };
|
||||||
|
@ -346,6 +348,8 @@
|
||||||
5BEDB720283B4AEA0078EB25 /* data-cht.plist */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; name = "data-cht.plist"; path = "Data/data-cht.plist"; sourceTree = "<group>"; };
|
5BEDB720283B4AEA0078EB25 /* data-cht.plist */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; name = "data-cht.plist"; path = "Data/data-cht.plist"; sourceTree = "<group>"; };
|
||||||
5BF0B84B28C070B000795FC6 /* NSEventExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSEventExtension.swift; sourceTree = "<group>"; };
|
5BF0B84B28C070B000795FC6 /* NSEventExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSEventExtension.swift; sourceTree = "<group>"; };
|
||||||
5BF255CD28B2694E003ECB60 /* vChewing-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "vChewing-Bridging-Header.h"; sourceTree = "<group>"; };
|
5BF255CD28B2694E003ECB60 /* vChewing-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "vChewing-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||||
|
5BF56F9728C39A2700DD6839 /* IMEState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IMEState.swift; sourceTree = "<group>"; };
|
||||||
|
5BF56F9928C39D1800DD6839 /* IMEStateData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IMEStateData.swift; sourceTree = "<group>"; };
|
||||||
5BF9DA2228840E6200DBD48E /* template-usersymbolphrases.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; lineEnding = 0; path = "template-usersymbolphrases.txt"; sourceTree = "<group>"; usesTabs = 0; };
|
5BF9DA2228840E6200DBD48E /* template-usersymbolphrases.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; lineEnding = 0; path = "template-usersymbolphrases.txt"; sourceTree = "<group>"; usesTabs = 0; };
|
||||||
5BF9DA2328840E6200DBD48E /* template-exclusions.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; lineEnding = 0; path = "template-exclusions.txt"; sourceTree = "<group>"; usesTabs = 0; };
|
5BF9DA2328840E6200DBD48E /* template-exclusions.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; lineEnding = 0; path = "template-exclusions.txt"; sourceTree = "<group>"; usesTabs = 0; };
|
||||||
5BF9DA2428840E6200DBD48E /* template-associatedPhrases-chs.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; lineEnding = 0; name = "template-associatedPhrases-chs.txt"; path = "../Data/components/chs/template-associatedPhrases-chs.txt"; sourceTree = "<group>"; usesTabs = 0; };
|
5BF9DA2428840E6200DBD48E /* template-associatedPhrases-chs.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; lineEnding = 0; name = "template-associatedPhrases-chs.txt"; path = "../Data/components/chs/template-associatedPhrases-chs.txt"; sourceTree = "<group>"; usesTabs = 0; };
|
||||||
|
@ -498,6 +502,8 @@
|
||||||
5B21176D28753B35000443A9 /* ctlInputMethod_HandleDisplay.swift */,
|
5B21176D28753B35000443A9 /* ctlInputMethod_HandleDisplay.swift */,
|
||||||
5B21176B287539BB000443A9 /* ctlInputMethod_HandleStates.swift */,
|
5B21176B287539BB000443A9 /* ctlInputMethod_HandleStates.swift */,
|
||||||
5BB802D927FABA8300CF1C19 /* ctlInputMethod_Menu.swift */,
|
5BB802D927FABA8300CF1C19 /* ctlInputMethod_Menu.swift */,
|
||||||
|
5BF56F9728C39A2700DD6839 /* IMEState.swift */,
|
||||||
|
5BF56F9928C39D1800DD6839 /* IMEStateData.swift */,
|
||||||
D461B791279DAC010070E734 /* InputState.swift */,
|
D461B791279DAC010070E734 /* InputState.swift */,
|
||||||
5BD0113C2818543900609769 /* KeyHandler_Core.swift */,
|
5BD0113C2818543900609769 /* KeyHandler_Core.swift */,
|
||||||
5B782EC3280C243C007276DE /* KeyHandler_HandleCandidate.swift */,
|
5B782EC3280C243C007276DE /* KeyHandler_HandleCandidate.swift */,
|
||||||
|
@ -1206,6 +1212,7 @@
|
||||||
5B7F225D2808501000DDD3CB /* KeyHandler_HandleInput.swift in Sources */,
|
5B7F225D2808501000DDD3CB /* KeyHandler_HandleInput.swift in Sources */,
|
||||||
5BA9FD1227FEDB6B002DE248 /* suiPrefPaneExperience.swift in Sources */,
|
5BA9FD1227FEDB6B002DE248 /* suiPrefPaneExperience.swift in Sources */,
|
||||||
D461B792279DAC010070E734 /* InputState.swift in Sources */,
|
D461B792279DAC010070E734 /* InputState.swift in Sources */,
|
||||||
|
5BF56F9828C39A2700DD6839 /* IMEState.swift in Sources */,
|
||||||
5B62A33D27AE7CC100A19448 /* ctlAboutWindow.swift in Sources */,
|
5B62A33D27AE7CC100A19448 /* ctlAboutWindow.swift in Sources */,
|
||||||
D47B92C027972AD100458394 /* main.swift in Sources */,
|
D47B92C027972AD100458394 /* main.swift in Sources */,
|
||||||
D4A13D5A27A59F0B003BE359 /* ctlInputMethod_Core.swift in Sources */,
|
D4A13D5A27A59F0B003BE359 /* ctlInputMethod_Core.swift in Sources */,
|
||||||
|
@ -1232,6 +1239,7 @@
|
||||||
D47F7DCE278BFB57002F9DD7 /* ctlPrefWindow.swift in Sources */,
|
D47F7DCE278BFB57002F9DD7 /* ctlPrefWindow.swift in Sources */,
|
||||||
5BD0113D2818543900609769 /* KeyHandler_Core.swift in Sources */,
|
5BD0113D2818543900609769 /* KeyHandler_Core.swift in Sources */,
|
||||||
5B2170E4289FACAD00BE7304 /* 2_Walker.swift in Sources */,
|
5B2170E4289FACAD00BE7304 /* 2_Walker.swift in Sources */,
|
||||||
|
5BF56F9A28C39D1800DD6839 /* IMEStateData.swift in Sources */,
|
||||||
5BA9FD4227FEF3C8002DE248 /* PreferencePane.swift in Sources */,
|
5BA9FD4227FEF3C8002DE248 /* PreferencePane.swift in Sources */,
|
||||||
5BA0DF312817857D009E73BB /* lmUserOverride.swift in Sources */,
|
5BA0DF312817857D009E73BB /* lmUserOverride.swift in Sources */,
|
||||||
5BA9FD8B28006B41002DE248 /* VDKComboBox.swift in Sources */,
|
5BA9FD8B28006B41002DE248 /* VDKComboBox.swift in Sources */,
|
||||||
|
|
Loading…
Reference in New Issue