diff --git a/Source/Modules/ControllerModules/ctlInputMethod_Common.swift b/Source/Modules/ControllerModules/ctlInputMethod_Common.swift new file mode 100644 index 00000000..6aafa997 --- /dev/null +++ b/Source/Modules/ControllerModules/ctlInputMethod_Common.swift @@ -0,0 +1,73 @@ +// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License). +// All possible vChewing-specific modifications are of: +// (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 InputMethodKit + +extension ctlInputMethod { + /// 完成 handle() 函式本該完成的內容,但去掉了與 IMK 選字窗有關的判斷語句。 + /// 這樣分開處理很有必要,不然 handle() 函式會陷入無限迴圈。 + /// - Parameter event: 由 IMK 選字窗接收的裝置操作輸入事件。 + /// - Returns: 回「`true`」以將該案件已攔截處理的訊息傳遞給 IMK;回「`false`」則放行、不作處理。 + func commonEventHandler(_ event: NSEvent!) -> Bool { + // 用 Shift 開關半形英數模式,僅對 macOS 10.15 及之後的 macOS 有效。 + let shouldUseHandle = + (IME.arrClientShiftHandlingExceptionList.contains(clientBundleIdentifier) + || mgrPrefs.shouldAlwaysUseShiftKeyAccommodation) + if #available(macOS 10.15, *) { + if ShiftKeyUpChecker.check(event), !mgrPrefs.disableShiftTogglingAlphanumericalMode { + if !shouldUseHandle || (!rencentKeyHandledByKeyHandler && shouldUseHandle) { + NotifierController.notify( + message: String( + format: "%@%@%@", NSLocalizedString("Alphanumerical Mode", comment: ""), "\n", + toggleASCIIMode() + ? NSLocalizedString("NotificationSwitchON", comment: "") + : NSLocalizedString("NotificationSwitchOFF", comment: "") + ) + ) + } + if shouldUseHandle { + rencentKeyHandledByKeyHandler = false + } + return false + } + } + + /// 這裡仍舊需要判斷 flags。之前使輸入法狀態卡住無法敲漢字的問題已在 KeyHandler 內修復。 + /// 這裡不判斷 flags 的話,用方向鍵前後定位光標之後,再次試圖觸發組字區時、反而會在首次按鍵時失敗。 + /// 同時注意:必須在 event.type == .flagsChanged 結尾插入 return false, + /// 否則,每次處理這種判斷時都會觸發 NSInternalInconsistencyException。 + if event.type == .flagsChanged { return false } + + // 準備修飾鍵,用來判定要新增的詞彙是否需要賦以非常低的權重。 + ctlInputMethod.areWeNerfing = event.modifierFlags.contains([.shift, .command]) + + var input = InputSignal(event: event, isVerticalTyping: isVerticalTyping) + input.isASCIIModeInput = isASCIIMode + + // 無法列印的訊號輸入,一概不作處理。 + // 這個過程不能放在 KeyHandler 內,否則不會起作用。 + if !input.charCode.isPrintable { + return false + } + + /// 將按鍵行為與當前輸入法狀態結合起來、交給按鍵調度模組來處理。 + /// 再根據返回的 result bool 數值來告知 IMK「這個按鍵事件是被處理了還是被放行了」。 + /// 這裡不用 keyHandler.handleCandidate() 是因為需要針對聯想詞輸入狀態做額外處理。 + let result = keyHandler.handle(input: input, state: state) { newState in + self.handle(state: newState) + } errorCallback: { + clsSFX.beep() + } + if shouldUseHandle { + rencentKeyHandledByKeyHandler = result + } + return result + } +} diff --git a/Source/Modules/ControllerModules/ctlInputMethod_Core.swift b/Source/Modules/ControllerModules/ctlInputMethod_Core.swift index 4a295e11..b2e7e7c3 100644 --- a/Source/Modules/ControllerModules/ctlInputMethod_Core.swift +++ b/Source/Modules/ControllerModules/ctlInputMethod_Core.swift @@ -205,64 +205,18 @@ class ctlInputMethod: IMKInputController { @objc(handleEvent:client:) override func handle(_ event: NSEvent!, client sender: Any!) -> Bool { _ = sender // 防止格式整理工具毀掉與此對應的參數。 - // 用 Shift 開關半形英數模式,僅對 macOS 10.15 及之後的 macOS 有效。 - let shouldUseHandle = - (IME.arrClientShiftHandlingExceptionList.contains(clientBundleIdentifier) - || mgrPrefs.shouldAlwaysUseShiftKeyAccommodation) - if #available(macOS 10.15, *) { - if ShiftKeyUpChecker.check(event), !mgrPrefs.disableShiftTogglingAlphanumericalMode { - if !shouldUseHandle || (!rencentKeyHandledByKeyHandler && shouldUseHandle) { - NotifierController.notify( - message: String( - format: "%@%@%@", NSLocalizedString("Alphanumerical Mode", comment: ""), "\n", - toggleASCIIMode() - ? NSLocalizedString("NotificationSwitchON", comment: "") - : NSLocalizedString("NotificationSwitchOFF", comment: "") - ) - ) - } - if shouldUseHandle { - rencentKeyHandledByKeyHandler = false - } - return false - } - } - - /// IMK 選字窗處理,當且僅當啟用了 IMK 選字窗的時候才會生效。 + // IMK 選字窗處理,當且僅當啟用了 IMK 選字窗的時候才會生效。 + // 這樣可以讓 interpretKeyEvents() 函式自行判斷: + // - 是就地交給 super.interpretKeyEvents() 處理? + // - 還是藉由 delegate 扔回 ctlInputMethod 給 KeyHandler 處理? if let ctlCandidateCurrent = ctlInputMethod.ctlCandidateCurrent as? ctlCandidateIMK, ctlCandidateCurrent.visible { ctlCandidateCurrent.interpretKeyEvents([event]) return true } - /// 這裡仍舊需要判斷 flags。之前使輸入法狀態卡住無法敲漢字的問題已在 KeyHandler 內修復。 - /// 這裡不判斷 flags 的話,用方向鍵前後定位光標之後,再次試圖觸發組字區時、反而會在首次按鍵時失敗。 - /// 同時注意:必須在 event.type == .flagsChanged 結尾插入 return false, - /// 否則,每次處理這種判斷時都會觸發 NSInternalInconsistencyException。 - if event.type == .flagsChanged { return false } - - // 準備修飾鍵,用來判定要新增的詞彙是否需要賦以非常低的權重。 - ctlInputMethod.areWeNerfing = event.modifierFlags.contains([.shift, .command]) - - var input = InputSignal(event: event, isVerticalTyping: isVerticalTyping) - input.isASCIIModeInput = isASCIIMode - - // 無法列印的訊號輸入,一概不作處理。 - // 這個過程不能放在 KeyHandler 內,否則不會起作用。 - if !input.charCode.isPrintable { - return false - } - - /// 將按鍵行為與當前輸入法狀態結合起來、交給按鍵調度模組來處理。 - /// 再根據返回的 result bool 數值來告知 IMK「這個按鍵事件是被處理了還是被放行了」。 - let result = keyHandler.handle(input: input, state: state) { newState in - self.handle(state: newState) - } errorCallback: { - clsSFX.beep() - } - if shouldUseHandle { - rencentKeyHandledByKeyHandler = result - } - return result + /// 我們不在這裡處理了,直接交給 commonEventHandler 來處理。 + /// 這樣可以與 IMK 選字窗共用按鍵處理資源,維護起來也比較方便。 + return commonEventHandler(event) } /// 有時會出現某些 App 攔截輸入法的 Ctrl+Enter / Shift+Enter 熱鍵的情況。 diff --git a/Source/Modules/ControllerModules/ctlInputMethod_Delegates.swift b/Source/Modules/ControllerModules/ctlInputMethod_Delegates.swift index 87ff91b9..58796923 100644 --- a/Source/Modules/ControllerModules/ctlInputMethod_Delegates.swift +++ b/Source/Modules/ControllerModules/ctlInputMethod_Delegates.swift @@ -54,64 +54,13 @@ extension ctlInputMethod: KeyHandlerDelegate { extension ctlInputMethod: ctlCandidateDelegate { var isAssociatedPhrasesMode: Bool { state is InputState.AssociatedPhrases } - /// 與 handle() 完全雷同,但去掉了與 IMK 選字窗有關的判斷語句。 - /// 這兩個函數最好分開處理,不然 handle() 函數會陷入無限迴圈。 + /// 完成 handle() 函式本該完成的內容,但去掉了與 IMK 選字窗有關的判斷語句。 + /// 這樣分開處理很有必要,不然 handle() 函式會陷入無限迴圈。 + /// 該函式僅由 IMK 選字窗來存取,且對接給 commonEventHandler()。 /// - Parameter event: 由 IMK 選字窗接收的裝置操作輸入事件。 /// - Returns: 回「`true`」以將該案件已攔截處理的訊息傳遞給 IMK;回「`false`」則放行、不作處理。 - func handleDelegateEvent(_ event: NSEvent!) -> Bool { - // 用 Shift 開關半形英數模式,僅對 macOS 10.15 及之後的 macOS 有效。 - let shouldUseHandle = - (IME.arrClientShiftHandlingExceptionList.contains(clientBundleIdentifier) - || mgrPrefs.shouldAlwaysUseShiftKeyAccommodation) - if #available(macOS 10.15, *) { - if ShiftKeyUpChecker.check(event), !mgrPrefs.disableShiftTogglingAlphanumericalMode { - if !shouldUseHandle || (!rencentKeyHandledByKeyHandler && shouldUseHandle) { - NotifierController.notify( - message: String( - format: "%@%@%@", NSLocalizedString("Alphanumerical Mode", comment: ""), "\n", - toggleASCIIMode() - ? NSLocalizedString("NotificationSwitchON", comment: "") - : NSLocalizedString("NotificationSwitchOFF", comment: "") - ) - ) - } - if shouldUseHandle { - rencentKeyHandledByKeyHandler = false - } - return false - } - } - - /// 這裡仍舊需要判斷 flags。之前使輸入法狀態卡住無法敲漢字的問題已在 KeyHandler 內修復。 - /// 這裡不判斷 flags 的話,用方向鍵前後定位光標之後,再次試圖觸發組字區時、反而會在首次按鍵時失敗。 - /// 同時注意:必須在 event.type == .flagsChanged 結尾插入 return false, - /// 否則,每次處理這種判斷時都會觸發 NSInternalInconsistencyException。 - if event.type == .flagsChanged { return false } - - // 準備修飾鍵,用來判定要新增的詞彙是否需要賦以非常低的權重。 - ctlInputMethod.areWeNerfing = event.modifierFlags.contains([.shift, .command]) - - var input = InputSignal(event: event, isVerticalTyping: isVerticalTyping) - input.isASCIIModeInput = isASCIIMode - - // 無法列印的訊號輸入,一概不作處理。 - // 這個過程不能放在 KeyHandler 內,否則不會起作用。 - if !input.charCode.isPrintable { - return false - } - - /// 將按鍵行為與當前輸入法狀態結合起來、交給按鍵調度模組來處理。 - /// 再根據返回的 result bool 數值來告知 IMK「這個按鍵事件是被處理了還是被放行了」。 - /// 這裡不用 keyHandler.handleCandidate() 是因為需要針對聯想詞輸入狀態做額外處理。 - let result = keyHandler.handle(input: input, state: state) { newState in - self.handle(state: newState) - } errorCallback: { - clsSFX.beep() - } - if shouldUseHandle { - rencentKeyHandledByKeyHandler = result - } - return result + @discardableResult func sharedEventHandler(_ event: NSEvent!) -> Bool { + commonEventHandler(event) } func candidateCountForController(_ controller: ctlCandidateProtocol) -> Int { diff --git a/Source/Modules/UIModules/CandidateUI/ctlCandidate.swift b/Source/Modules/UIModules/CandidateUI/ctlCandidate.swift index a4aea23a..f13943e4 100644 --- a/Source/Modules/UIModules/CandidateUI/ctlCandidate.swift +++ b/Source/Modules/UIModules/CandidateUI/ctlCandidate.swift @@ -28,7 +28,7 @@ public class CandidateKeyLabel: NSObject { public protocol ctlCandidateDelegate: AnyObject { var isAssociatedPhrasesMode: Bool { get } - func handleDelegateEvent(_ event: NSEvent!) -> Bool + func sharedEventHandler(_ event: NSEvent!) -> Bool func candidateCountForController(_ controller: ctlCandidateProtocol) -> Int func candidatesForController(_ controller: ctlCandidateProtocol) -> [(String, String)] func ctlCandidate(_ controller: ctlCandidateProtocol, candidateAtIndex index: Int) diff --git a/Source/Modules/UIModules/CandidateUI/ctlCandidateIMK.swift b/Source/Modules/UIModules/CandidateUI/ctlCandidateIMK.swift index c221714a..72c4fadb 100644 --- a/Source/Modules/UIModules/CandidateUI/ctlCandidateIMK.swift +++ b/Source/Modules/UIModules/CandidateUI/ctlCandidateIMK.swift @@ -249,7 +249,7 @@ public class ctlCandidateIMK: IMKCandidates, ctlCandidateProtocol { let input = InputSignal(event: event) guard let delegate = delegate else { return } if input.isEsc || input.isBackSpace || input.isDelete || (input.isShiftHold && !input.isSpace) { - _ = delegate.handleDelegateEvent(event) + _ = delegate.sharedEventHandler(event) } else if input.isSymbolMenuPhysicalKey || input.isSpace { if input.isShiftHold { switch currentLayout { @@ -308,7 +308,7 @@ public class ctlCandidateIMK: IMKCandidates, ctlCandidateProtocol { !input.isCursorClockLeft, !input.isCursorClockRight, !input.isSpace, !input.isEnter || !mgrPrefs.alsoConfirmAssociatedCandidatesByEnter { - _ = delegate.handleDelegateEvent(event) + _ = delegate.sharedEventHandler(event) return } super.interpretKeyEvents(eventArray) diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index a971a1cf..d6f3af2b 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -38,6 +38,7 @@ 5B62A34927AE7CD900A19448 /* TooltipController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A34327AE7CD900A19448 /* TooltipController.swift */; }; 5B62A34A27AE7CD900A19448 /* NotifierController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A34527AE7CD900A19448 /* NotifierController.swift */; }; 5B62A35327AE89C400A19448 /* InputSourceHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B62A33127AE792F00A19448 /* InputSourceHelper.swift */; }; + 5B6C141228A9D4B30098ADF8 /* ctlInputMethod_Common.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6C141128A9D4B30098ADF8 /* ctlInputMethod_Common.swift */; }; 5B73FB5E27B2BE1300E9BF49 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5B73FB6027B2BE1300E9BF49 /* InfoPlist.strings */; }; 5B782EC4280C243C007276DE /* KeyHandler_HandleCandidate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B782EC3280C243C007276DE /* KeyHandler_HandleCandidate.swift */; }; 5B78EE0D28A562B4009456C1 /* suiPrefPaneDevZone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B78EE0C28A562B4009456C1 /* suiPrefPaneDevZone.swift */; }; @@ -244,6 +245,7 @@ 5B62A34327AE7CD900A19448 /* TooltipController.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = TooltipController.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; 5B62A34527AE7CD900A19448 /* NotifierController.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = NotifierController.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; 5B65B919284D0185007C558B /* README-CHT.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "README-CHT.md"; sourceTree = ""; }; + 5B6C141128A9D4B30098ADF8 /* ctlInputMethod_Common.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ctlInputMethod_Common.swift; sourceTree = ""; }; 5B73FB5427B2BD6900E9BF49 /* PhraseEditor-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "PhraseEditor-Info.plist"; path = "UserPhraseEditor/PhraseEditor-Info.plist"; sourceTree = SOURCE_ROOT; }; 5B73FB5F27B2BE1300E9BF49 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 5B782EC3280C243C007276DE /* KeyHandler_HandleCandidate.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = KeyHandler_HandleCandidate.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; @@ -468,6 +470,7 @@ children = ( 5B11328827B94CFB00E58451 /* AppleKeyboardConverter.swift */, D4A13D5927A59D5C003BE359 /* ctlInputMethod_Core.swift */, + 5B6C141128A9D4B30098ADF8 /* ctlInputMethod_Common.swift */, 5B21176F28753B9D000443A9 /* ctlInputMethod_Delegates.swift */, 5B21176D28753B35000443A9 /* ctlInputMethod_HandleDisplay.swift */, 5B21176B287539BB000443A9 /* ctlInputMethod_HandleStates.swift */, @@ -1188,6 +1191,7 @@ 5BA9FD4327FEF3C8002DE248 /* Preferences.swift in Sources */, 5BA9FD4427FEF3C8002DE248 /* SegmentedControlStyleViewController.swift in Sources */, 5B78EE0D28A562B4009456C1 /* suiPrefPaneDevZone.swift in Sources */, + 5B6C141228A9D4B30098ADF8 /* ctlInputMethod_Common.swift in Sources */, D47F7DCE278BFB57002F9DD7 /* ctlPrefWindow.swift in Sources */, 5BD0113D2818543900609769 /* KeyHandler_Core.swift in Sources */, 5B2170E4289FACAD00BE7304 /* 2_Walker.swift in Sources */,