KeyHandler // Make handleComposition() standalone.
This commit is contained in:
parent
ff8665757b
commit
5a94d3dd44
|
@ -0,0 +1,152 @@
|
|||
// Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
// Refactored from the ObjCpp-version of this class by:
|
||||
// (c) 2011 and onwards The OpenVanilla Project (MIT License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
1. The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
2. No trademark license is granted to use the trade names, trademarks, service
|
||||
marks, or product names of Contributor, except as required to fulfill notice
|
||||
requirements above.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/// 該檔案用來處理 KeyHandler.HandleInput() 當中的與組字有關的行為。
|
||||
|
||||
extension KeyHandler {
|
||||
/// 用來處理 KeyHandler.HandleInput() 當中的與組字有關的行為。
|
||||
/// - Parameters:
|
||||
/// - input: 輸入訊號。
|
||||
/// - state: 給定狀態(通常為當前狀態)。
|
||||
/// - stateCallback: 狀態回呼,交給對應的型別內的專有函式來處理。
|
||||
/// - errorCallback: 錯誤回呼。
|
||||
/// - Returns: 告知 IMK「該按鍵是否已經被輸入法攔截處理」。
|
||||
func handleComposition(
|
||||
input: InputSignal,
|
||||
state: InputStateProtocol,
|
||||
stateCallback: @escaping (InputStateProtocol) -> Void,
|
||||
errorCallback: @escaping () -> Void
|
||||
) -> Bool? {
|
||||
|
||||
// MARK: 注音按鍵輸入處理 (Handle BPMF Keys)
|
||||
|
||||
var keyConsumedByReading = false
|
||||
let skipPhoneticHandling = input.isReservedKey || input.isControlHold || input.isOptionHold
|
||||
|
||||
// 這裡 inputValidityCheck() 是讓注拼槽檢查 charCode 這個 UniChar 是否是合法的注音輸入。
|
||||
// 如果是的話,就將這次傳入的這個按鍵訊號塞入注拼槽內且標記為「keyConsumedByReading」。
|
||||
// 函式 composer.receiveKey() 可以既接收 String 又接收 UniChar。
|
||||
if !skipPhoneticHandling && composer.inputValidityCheck(key: input.charCode) {
|
||||
composer.receiveKey(fromCharCode: input.charCode)
|
||||
keyConsumedByReading = true
|
||||
|
||||
// 沒有調號的話,只需要 updateClientComposingBuffer() 且終止處理(return true)即可。
|
||||
// 有調號的話,則不需要這樣,而是轉而繼續在此之後的處理。
|
||||
if !composer.hasToneMarker() {
|
||||
stateCallback(buildInputtingState)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
var composeReading = composer.hasToneMarker() // 這裡不需要做排他性判斷。
|
||||
|
||||
// 如果當前的按鍵是 Enter 或 Space 的話,這時就可以取出 _composer 內的注音來做檢查了。
|
||||
// 來看看詞庫內到底有沒有對應的讀音索引。這裡用了類似「|=」的判斷處理方式。
|
||||
composeReading = composeReading || (!composer.isEmpty && (input.isSpace || input.isEnter))
|
||||
if composeReading {
|
||||
if input.isSpace, !composer.hasToneMarker() {
|
||||
// 補上空格,否則倚天忘形與許氏排列某些音無法響應不了陰平聲調。
|
||||
// 小麥注音因為使用 OVMandarin,所以不需要這樣補。但鐵恨引擎對所有聲調一視同仁。
|
||||
composer.receiveKey(fromString: " ")
|
||||
}
|
||||
let readingKey = composer.getComposition() // 拿取用來進行索引檢索用的注音。
|
||||
// 如果輸入法的辭典索引是漢語拼音的話,要注意上一行拿到的內容得是漢語拼音。
|
||||
|
||||
// 向語言模型詢問是否有對應的記錄。
|
||||
if !currentLM.hasUnigramsFor(key: readingKey) {
|
||||
IME.prtDebugIntel("B49C0979:語彙庫內無「\(readingKey)」的匹配記錄。")
|
||||
errorCallback()
|
||||
composer.clear()
|
||||
// 根據「組字器是否為空」來判定回呼哪一種狀態。
|
||||
stateCallback((compositor.isEmpty) ? InputState.EmptyIgnoringPreviousState() : buildInputtingState)
|
||||
return true // 向 IMK 報告說這個按鍵訊號已經被輸入法攔截處理了。
|
||||
}
|
||||
|
||||
// 將該讀音插入至組字器內的軌格當中。
|
||||
compositor.insertReading(readingKey)
|
||||
|
||||
// 讓組字器反爬軌格。
|
||||
let textToCommit = commitOverflownCompositionAndWalk
|
||||
|
||||
// 看看半衰記憶模組是否會對目前的狀態給出自動選字建議。
|
||||
fetchAndApplySuggestionsFromUserOverrideModel()
|
||||
|
||||
// 將組字器內超出最大動態爬軌範圍的節錨都標記為「已經手動選字過」,減少之後的爬軌運算負擔。
|
||||
markNodesFixedIfNecessary()
|
||||
|
||||
// 之後就是更新組字區了。先清空注拼槽的內容。
|
||||
composer.clear()
|
||||
|
||||
// 再以回呼組字狀態的方式來執行 updateClientComposingBuffer()。
|
||||
let inputting = buildInputtingState
|
||||
inputting.textToCommit = textToCommit
|
||||
stateCallback(inputting)
|
||||
|
||||
/// 逐字選字模式的處理。
|
||||
if mgrPrefs.useSCPCTypingMode {
|
||||
let choosingCandidates: InputState.ChoosingCandidate = buildCandidate(
|
||||
state: inputting,
|
||||
isTypingVertical: input.isTypingVertical
|
||||
)
|
||||
if choosingCandidates.candidates.count == 1 {
|
||||
clear()
|
||||
let reading: String = choosingCandidates.candidates.first?.0 ?? ""
|
||||
let text: String = choosingCandidates.candidates.first?.1 ?? ""
|
||||
stateCallback(InputState.Committing(textToCommit: text))
|
||||
|
||||
if !mgrPrefs.associatedPhrasesEnabled {
|
||||
stateCallback(InputState.Empty())
|
||||
} else {
|
||||
if let associatedPhrases =
|
||||
buildAssociatePhraseState(
|
||||
withPair: .init(key: reading, value: text),
|
||||
isTypingVertical: input.isTypingVertical
|
||||
), !associatedPhrases.candidates.isEmpty
|
||||
{
|
||||
stateCallback(associatedPhrases)
|
||||
} else {
|
||||
stateCallback(InputState.Empty())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
stateCallback(choosingCandidates)
|
||||
}
|
||||
}
|
||||
// 將「這個按鍵訊號已經被輸入法攔截處理了」的結果藉由 ctlInputMethod 回報給 IMK。
|
||||
return true
|
||||
}
|
||||
|
||||
/// 如果此時這個選項是 true 的話,可知當前注拼槽輸入了聲調、且上一次按鍵不是聲調按鍵。
|
||||
/// 比方說大千傳統佈局敲「6j」會出現「ˊㄨ」但並不會被認為是「ㄨˊ」,因為先輸入的調號
|
||||
/// 並非用來確認這個注音的調號。除非是:「ㄨˊ」「ˊㄨˊ」「ˊㄨˇ」「ˊㄨ 」等。
|
||||
if keyConsumedByReading {
|
||||
// 以回呼組字狀態的方式來執行 updateClientComposingBuffer()。
|
||||
stateCallback(buildInputtingState)
|
||||
return true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
|
@ -158,109 +158,10 @@ extension KeyHandler {
|
|||
|
||||
// MARK: 注音按鍵輸入處理 (Handle BPMF Keys)
|
||||
|
||||
var keyConsumedByReading = false
|
||||
let skipPhoneticHandling = input.isReservedKey || input.isControlHold || input.isOptionHold
|
||||
|
||||
// 這裡 inputValidityCheck() 是讓注拼槽檢查 charCode 這個 UniChar 是否是合法的注音輸入。
|
||||
// 如果是的話,就將這次傳入的這個按鍵訊號塞入注拼槽內且標記為「keyConsumedByReading」。
|
||||
// 函式 composer.receiveKey() 可以既接收 String 又接收 UniChar。
|
||||
if !skipPhoneticHandling && composer.inputValidityCheck(key: charCode) {
|
||||
composer.receiveKey(fromCharCode: charCode)
|
||||
keyConsumedByReading = true
|
||||
|
||||
// 沒有調號的話,只需要 updateClientComposingBuffer() 且終止處理(return true)即可。
|
||||
// 有調號的話,則不需要這樣,而是轉而繼續在此之後的處理。
|
||||
if !composer.hasToneMarker() {
|
||||
stateCallback(buildInputtingState)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
var composeReading = composer.hasToneMarker() // 這裡不需要做排他性判斷。
|
||||
|
||||
// 如果當前的按鍵是 Enter 或 Space 的話,這時就可以取出 _composer 內的注音來做檢查了。
|
||||
// 來看看詞庫內到底有沒有對應的讀音索引。這裡用了類似「|=」的判斷處理方式。
|
||||
composeReading = composeReading || (!composer.isEmpty && (input.isSpace || input.isEnter))
|
||||
if composeReading {
|
||||
if input.isSpace, !composer.hasToneMarker() {
|
||||
// 補上空格,否則倚天忘形與許氏排列某些音無法響應不了陰平聲調。
|
||||
// 小麥注音因為使用 OVMandarin,所以不需要這樣補。但鐵恨引擎對所有聲調一視同仁。
|
||||
composer.receiveKey(fromString: " ")
|
||||
}
|
||||
let readingKey = composer.getComposition() // 拿取用來進行索引檢索用的注音。
|
||||
// 如果輸入法的辭典索引是漢語拼音的話,要注意上一行拿到的內容得是漢語拼音。
|
||||
|
||||
// 向語言模型詢問是否有對應的記錄。
|
||||
if !currentLM.hasUnigramsFor(key: readingKey) {
|
||||
IME.prtDebugIntel("B49C0979:語彙庫內無「\(readingKey)」的匹配記錄。")
|
||||
errorCallback()
|
||||
composer.clear()
|
||||
// 根據「組字器是否為空」來判定回呼哪一種狀態。
|
||||
stateCallback((compositor.isEmpty) ? InputState.EmptyIgnoringPreviousState() : buildInputtingState)
|
||||
return true // 向 IMK 報告說這個按鍵訊號已經被輸入法攔截處理了。
|
||||
}
|
||||
|
||||
// 將該讀音插入至組字器內的軌格當中。
|
||||
compositor.insertReading(readingKey)
|
||||
|
||||
// 讓組字器反爬軌格。
|
||||
let textToCommit = commitOverflownCompositionAndWalk
|
||||
|
||||
// 看看半衰記憶模組是否會對目前的狀態給出自動選字建議。
|
||||
fetchAndApplySuggestionsFromUserOverrideModel()
|
||||
|
||||
// 將組字器內超出最大動態爬軌範圍的節錨都標記為「已經手動選字過」,減少之後的爬軌運算負擔。
|
||||
markNodesFixedIfNecessary()
|
||||
|
||||
// 之後就是更新組字區了。先清空注拼槽的內容。
|
||||
composer.clear()
|
||||
|
||||
// 再以回呼組字狀態的方式來執行 updateClientComposingBuffer()。
|
||||
let inputting = buildInputtingState
|
||||
inputting.textToCommit = textToCommit
|
||||
stateCallback(inputting)
|
||||
|
||||
/// 逐字選字模式的處理。
|
||||
if mgrPrefs.useSCPCTypingMode {
|
||||
let choosingCandidates: InputState.ChoosingCandidate = buildCandidate(
|
||||
state: inputting,
|
||||
isTypingVertical: input.isTypingVertical
|
||||
)
|
||||
if choosingCandidates.candidates.count == 1 {
|
||||
clear()
|
||||
let reading: String = choosingCandidates.candidates.first?.0 ?? ""
|
||||
let text: String = choosingCandidates.candidates.first?.1 ?? ""
|
||||
stateCallback(InputState.Committing(textToCommit: text))
|
||||
|
||||
if !mgrPrefs.associatedPhrasesEnabled {
|
||||
stateCallback(InputState.Empty())
|
||||
} else {
|
||||
if let associatedPhrases =
|
||||
buildAssociatePhraseState(
|
||||
withPair: .init(key: reading, value: text),
|
||||
isTypingVertical: input.isTypingVertical
|
||||
), !associatedPhrases.candidates.isEmpty
|
||||
{
|
||||
stateCallback(associatedPhrases)
|
||||
} else {
|
||||
stateCallback(InputState.Empty())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
stateCallback(choosingCandidates)
|
||||
}
|
||||
}
|
||||
// 將「這個按鍵訊號已經被輸入法攔截處理了」的結果藉由 ctlInputMethod 回報給 IMK。
|
||||
return true
|
||||
}
|
||||
|
||||
/// 如果此時這個選項是 true 的話,可知當前注拼槽輸入了聲調、且上一次按鍵不是聲調按鍵。
|
||||
/// 比方說大千傳統佈局敲「6j」會出現「ˊㄨ」但並不會被認為是「ㄨˊ」,因為先輸入的調號
|
||||
/// 並非用來確認這個注音的調號。除非是:「ㄨˊ」「ˊㄨˊ」「ˊㄨˇ」「ˊㄨ 」等。
|
||||
if keyConsumedByReading {
|
||||
// 以回呼組字狀態的方式來執行 updateClientComposingBuffer()。
|
||||
stateCallback(buildInputtingState)
|
||||
return true
|
||||
if let compositionHandled = handleComposition(
|
||||
input: input, state: state, stateCallback: stateCallback, errorCallback: errorCallback)
|
||||
{
|
||||
return compositionHandled
|
||||
}
|
||||
|
||||
// MARK: 用上下左右鍵呼叫選字窗 (Calling candidate window using Up / Down or PageUp / PageDn.)
|
||||
|
|
|
@ -102,6 +102,7 @@
|
|||
5BD05C6A27B2BBEF004C4F1D /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BD05C6527B2BBEF004C4F1D /* ViewController.swift */; };
|
||||
5BDC1CFA27FDF1310052C2B9 /* apiUpdate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BDC1CF927FDF1310052C2B9 /* apiUpdate.swift */; };
|
||||
5BDCBB2E27B4E67A00D0CC59 /* vChewingPhraseEditor.app in Resources */ = {isa = PBXBuildFile; fileRef = 5BD05BB827B2A429004C4F1D /* vChewingPhraseEditor.app */; };
|
||||
5BE377A0288FED8D0037365B /* KeyHandler_HandleComposition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BE3779F288FED8D0037365B /* KeyHandler_HandleComposition.swift */; };
|
||||
5BE78BD927B3775B005EA1BE /* ctlAboutWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BE78BD827B37750005EA1BE /* ctlAboutWindow.swift */; };
|
||||
5BE78BDD27B3776D005EA1BE /* frmAboutWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5BE78BDA27B37764005EA1BE /* frmAboutWindow.xib */; };
|
||||
5BEDB721283B4C250078EB25 /* data-cns.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5BEDB71D283B4AEA0078EB25 /* data-cns.plist */; };
|
||||
|
@ -303,6 +304,7 @@
|
|||
5BDCBB4A27B4F6C700D0CC59 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||
5BDCBB4B27B4F6C700D0CC59 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/frmAboutWindow.strings"; sourceTree = "<group>"; };
|
||||
5BDCBB4D27B4F6C700D0CC59 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
||||
5BE3779F288FED8D0037365B /* KeyHandler_HandleComposition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyHandler_HandleComposition.swift; sourceTree = "<group>"; };
|
||||
5BE78BD827B37750005EA1BE /* ctlAboutWindow.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = ctlAboutWindow.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
|
||||
5BE78BDB27B37764005EA1BE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/frmAboutWindow.xib; sourceTree = "<group>"; };
|
||||
5BE78BDF27B37968005EA1BE /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/frmAboutWindow.strings; sourceTree = "<group>"; };
|
||||
|
@ -465,6 +467,7 @@
|
|||
5BD0113C2818543900609769 /* KeyHandler_Core.swift */,
|
||||
5B782EC3280C243C007276DE /* KeyHandler_HandleCandidate.swift */,
|
||||
5B7F225C2808501000DDD3CB /* KeyHandler_HandleInput.swift */,
|
||||
5BE3779F288FED8D0037365B /* KeyHandler_HandleComposition.swift */,
|
||||
5B3133BE280B229700A4A505 /* KeyHandler_States.swift */,
|
||||
5B62A33727AE79CD00A19448 /* StringUtils.swift */,
|
||||
5BAA8FBD282CAF380066C406 /* SyllableComposer.swift */,
|
||||
|
@ -1204,6 +1207,7 @@
|
|||
5BA9FD3F27FEF3C8002DE248 /* Pane.swift in Sources */,
|
||||
5BB802DA27FABA8300CF1C19 /* ctlInputMethod_Menu.swift in Sources */,
|
||||
5B38F5A1281E2E49007D5F5D /* 1_Compositor.swift in Sources */,
|
||||
5BE377A0288FED8D0037365B /* KeyHandler_HandleComposition.swift in Sources */,
|
||||
5BDC1CFA27FDF1310052C2B9 /* apiUpdate.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
|
Loading…
Reference in New Issue