diff --git a/Packages/vChewing_MainAssembly/Sources/MainAssembly/InputHandler/InputHandler_HandleCandidate.swift b/Packages/vChewing_MainAssembly/Sources/MainAssembly/InputHandler/InputHandler_HandleCandidate.swift index fdaf93fd..51135bc3 100644 --- a/Packages/vChewing_MainAssembly/Sources/MainAssembly/InputHandler/InputHandler_HandleCandidate.swift +++ b/Packages/vChewing_MainAssembly/Sources/MainAssembly/InputHandler/InputHandler_HandleCandidate.swift @@ -29,6 +29,7 @@ extension InputHandler { guard state.isCandidateContainer else { return false } // 會自動判斷「isEmpty」。 guard ctlCandidate.visible else { return false } let inputText = ignoringModifiers ? (input.inputTextIgnoringModifiers ?? input.text) : input.text + let allowMovingCompositorCursor = state.type == .ofCandidates && !prefs.useSCPCTypingMode // MARK: 選字窗內使用熱鍵升權、降權、刪詞。 @@ -151,7 +152,7 @@ extension InputHandler { return true case .kUpArrow, .kDownArrow, .kLeftArrow, .kRightArrow: switch input.commonKeyModifierFlags { - case [.option, .shift] where input.isCursorForward: + case [.option, .shift] where allowMovingCompositorCursor && input.isCursorForward: if compositor.cursor < compositor.length { compositor.cursor += 1 if isCursorCuttingChar() { compositor.jumpCursorBySpan(to: .front) } @@ -160,7 +161,7 @@ extension InputHandler { delegate.callError("D3006C85") } return true - case [.option, .shift] where input.isCursorBackward: + case [.option, .shift] where allowMovingCompositorCursor && input.isCursorBackward: if compositor.cursor > 0 { compositor.cursor -= 1 if isCursorCuttingChar() { compositor.jumpCursorBySpan(to: .rear) } @@ -214,21 +215,54 @@ extension InputHandler { } } + // MARK: J / K 鍵組字區的游標移動行為處理 + + let allowMovingCompositorCursorByJK = allowMovingCompositorCursor && prefs.useJKtoMoveCompositorCursorInCandidateState + + checkMovingCompositorCursorByJK: if allowMovingCompositorCursorByJK { + guard input.keyModifierFlags.isEmpty else { break checkMovingCompositorCursorByJK } + // keycode: 38 = J, 40 = K. + switch input.keyCode { + case 38: + if compositor.cursor > 0 { + compositor.cursor -= 1 + if isCursorCuttingChar() { compositor.jumpCursorBySpan(to: .rear) } + delegate.switchState(generateStateOfCandidates()) + } else { + delegate.callError("6F389AE9") + } + return true + case 40: + if compositor.cursor < compositor.length { + compositor.cursor += 1 + if isCursorCuttingChar() { compositor.jumpCursorBySpan(to: .front) } + delegate.switchState(generateStateOfCandidates()) + } else { + delegate.callError("EDBD27F2") + } + return true + default: break checkMovingCompositorCursorByJK + } + } + // MARK: 關聯詞語處理 (Associated Phrases) 以及標準選字處理 if state.type == .ofAssociates, !input.isShiftHold { return false } var index: Int? var shaltShiftHold = [.ofAssociates].contains(state.type) - if [.ofInputting].contains(state.type) { + if state.type == .ofInputting { let cassetteShift = currentLM.areCassetteCandidateKeysShiftHeld shaltShiftHold = shaltShiftHold || cassetteShift } - let matched: String = shaltShiftHold ? input.inputTextIgnoringModifiers ?? "" : inputText - checkSelectionKey: for keyPair in delegate.selectionKeys.enumerated() { - guard matched.lowercased() == keyPair.element.lowercased() else { continue } - index = Int(keyPair.offset) - break checkSelectionKey + let matched: String = (shaltShiftHold ? input.inputTextIgnoringModifiers ?? "" : inputText).lowercased() + // 如果允許 J / K 鍵前後移動組字區游標的話,則不再將 J / K 鍵盤視為選字鍵。 + if !(prefs.useJKtoMoveCompositorCursorInCandidateState && "jk".contains(matched)) { + checkSelectionKey: for keyPair in delegate.selectionKeys.enumerated() { + guard matched == keyPair.element.lowercased() else { continue } + index = Int(keyPair.offset) + break checkSelectionKey + } } // 標準選字處理 diff --git a/Packages/vChewing_MainAssembly/Sources/MainAssembly/PrefMgr_Core.swift b/Packages/vChewing_MainAssembly/Sources/MainAssembly/PrefMgr_Core.swift index b804bf6a..9758fa0b 100644 --- a/Packages/vChewing_MainAssembly/Sources/MainAssembly/PrefMgr_Core.swift +++ b/Packages/vChewing_MainAssembly/Sources/MainAssembly/PrefMgr_Core.swift @@ -103,6 +103,9 @@ import SwiftExtension @AppProperty(key: UserDef.kUseRearCursorMode.rawValue, defaultValue: false) public dynamic var useRearCursorMode: Bool + @AppProperty(key: UserDef.kUseJKtoMoveCompositorCursorInCandidateState.rawValue, defaultValue: false) + public var useJKtoMoveCompositorCursorInCandidateState: Bool + @AppProperty(key: UserDef.kMoveCursorAfterSelectingCandidate.rawValue, defaultValue: true) public dynamic var moveCursorAfterSelectingCandidate: Bool diff --git a/Packages/vChewing_MainAssembly/Sources/MainAssembly/Settings/SettingsCocoa/VwrSettingsPaneCocoaCandidates.swift b/Packages/vChewing_MainAssembly/Sources/MainAssembly/Settings/SettingsCocoa/VwrSettingsPaneCocoaCandidates.swift index 0d90f548..ea218e78 100644 --- a/Packages/vChewing_MainAssembly/Sources/MainAssembly/Settings/SettingsCocoa/VwrSettingsPaneCocoaCandidates.swift +++ b/Packages/vChewing_MainAssembly/Sources/MainAssembly/Settings/SettingsCocoa/VwrSettingsPaneCocoaCandidates.swift @@ -27,13 +27,6 @@ public extension SettingsPanesCocoa { NSView().makeSimpleConstraint(.height, relation: .equal, value: 4) NSTabView.build { NSTabView.TabPage(title: "A") { - NSStackView.buildSection(width: contentWidth) { - UserDef.kCandidateKeys.render { renderable in - renderable.currentControl?.target = self - renderable.currentControl?.action = #selector(self.candidateKeysDidSet(_:)) - renderable.currentControl?.alignment = .right - } - }?.boxed() NSStackView.buildSection(width: contentWidth) { UserDef.kUseHorizontalCandidateList.render(fixWidth: contentWidth) UserDef.kCandidateListTextSize.render { renderable in @@ -47,12 +40,23 @@ public extension SettingsPanesCocoa { NSView() } NSTabView.TabPage(title: "B") { + NSStackView.buildSection(width: contentWidth) { + UserDef.kCandidateKeys.render { renderable in + renderable.currentControl?.target = self + renderable.currentControl?.action = #selector(self.candidateKeysDidSet(_:)) + renderable.currentControl?.alignment = .right + } + }?.boxed() NSStackView.buildSection(width: contentWidth) { UserDef.kUseRearCursorMode.render(fixWidth: contentWidth) UserDef.kMoveCursorAfterSelectingCandidate.render(fixWidth: contentWidth) UserDef.kUseDynamicCandidateWindowOrigin.render(fixWidth: contentWidth) UserDef.kDodgeInvalidEdgeCandidateCursorPosition.render(fixWidth: contentWidth) + UserDef.kUseJKtoMoveCompositorCursorInCandidateState.render(fixWidth: contentWidth) }?.boxed() + NSView() + } + NSTabView.TabPage(title: "C") { NSStackView.buildSection(width: contentWidth) { UserDef.kShowReverseLookupInCandidateUI.render(fixWidth: contentWidth) UserDef.kUseFixedCandidateOrderOnSelection.render(fixWidth: contentWidth) diff --git a/Packages/vChewing_MainAssembly/Sources/MainAssembly/Settings/SettingsUI/VwrSettingsPaneCandidates.swift b/Packages/vChewing_MainAssembly/Sources/MainAssembly/Settings/SettingsUI/VwrSettingsPaneCandidates.swift index 7c2ec3fd..a50fe424 100644 --- a/Packages/vChewing_MainAssembly/Sources/MainAssembly/Settings/SettingsUI/VwrSettingsPaneCandidates.swift +++ b/Packages/vChewing_MainAssembly/Sources/MainAssembly/Settings/SettingsUI/VwrSettingsPaneCandidates.swift @@ -35,6 +35,9 @@ public struct VwrSettingsPaneCandidates: View { @AppStorage(wrappedValue: false, UserDef.kUseRearCursorMode.rawValue) private var useRearCursorMode: Bool + @AppStorage(wrappedValue: false, UserDef.kUseJKtoMoveCompositorCursorInCandidateState.rawValue) + private var useJKtoMoveCompositorCursorInCandidateState: Bool + @AppStorage(wrappedValue: true, UserDef.kMoveCursorAfterSelectingCandidate.rawValue) private var moveCursorAfterSelectingCandidate: Bool @@ -66,6 +69,7 @@ public struct VwrSettingsPaneCandidates: View { .disabled(useRearCursorMode) } UserDef.kDodgeInvalidEdgeCandidateCursorPosition.bind($dodgeInvalidEdgeCandidateCursorPosition).render() + UserDef.kUseJKtoMoveCompositorCursorInCandidateState.bind($useJKtoMoveCompositorCursorInCandidateState).render() } Section { VwrSettingsPaneCandidates_SelectionKeys() diff --git a/Packages/vChewing_Shared/Sources/Shared/Protocols/PrefMgrProtocol.swift b/Packages/vChewing_Shared/Sources/Shared/Protocols/PrefMgrProtocol.swift index e5be05f9..7bb7b440 100644 --- a/Packages/vChewing_Shared/Sources/Shared/Protocols/PrefMgrProtocol.swift +++ b/Packages/vChewing_Shared/Sources/Shared/Protocols/PrefMgrProtocol.swift @@ -30,6 +30,7 @@ public protocol PrefMgrProtocol { var candidateWindowShowOnlyOneLine: Bool { get set } var shouldAutoReloadUserDataFiles: Bool { get set } var useRearCursorMode: Bool { get set } + var useJKtoMoveCompositorCursorInCandidateState: Bool { get set } var moveCursorAfterSelectingCandidate: Bool { get set } var dodgeInvalidEdgeCandidateCursorPosition: Bool { get set } var useDynamicCandidateWindowOrigin: Bool { get set } diff --git a/Packages/vChewing_Shared/Sources/Shared/UserDef/UserDef.swift b/Packages/vChewing_Shared/Sources/Shared/UserDef/UserDef.swift index c2009c33..35c62348 100644 --- a/Packages/vChewing_Shared/Sources/Shared/UserDef/UserDef.swift +++ b/Packages/vChewing_Shared/Sources/Shared/UserDef/UserDef.swift @@ -52,6 +52,7 @@ public enum UserDef: String, CaseIterable, Identifiable { case kAppleLanguages = "AppleLanguages" case kShouldAutoReloadUserDataFiles = "ShouldAutoReloadUserDataFiles" case kUseRearCursorMode = "UseRearCursorMode" + case kUseJKtoMoveCompositorCursorInCandidateState = "UseJKtoMoveCompositorCursorInCandidateState" case kUseDynamicCandidateWindowOrigin = "UseDynamicCandidateWindowOrigin" case kUseHorizontalCandidateList = "UseHorizontalCandidateList" case kChooseCandidateUsingSpace = "ChooseCandidateUsingSpace" @@ -173,6 +174,7 @@ public extension UserDef { case .kAppleLanguages: return .array case .kShouldAutoReloadUserDataFiles: return .bool case .kUseRearCursorMode: return .bool + case .kUseJKtoMoveCompositorCursorInCandidateState: return .bool case .kUseDynamicCandidateWindowOrigin: return .bool case .kUseHorizontalCandidateList: return .bool case .kChooseCandidateUsingSpace: return .bool @@ -328,6 +330,11 @@ public extension UserDef { 1: "at the rear of the phrase (like Microsoft New Phonetic)", ] ) + case .kUseJKtoMoveCompositorCursorInCandidateState: return .init( + userDef: self, + shortTitle: "i18n:UserDef.kUseJKtoMoveCompositorCursorInCandidateState.shortTitle", + description: "i18n:UserDef.kUseJKtoMoveCompositorCursorInCandidateState.description" + ) case .kUseDynamicCandidateWindowOrigin: return .init( userDef: self, shortTitle: "Adjust candidate window location according to current node length" ) diff --git a/Source/Resources/en.lproj/Localizable.strings b/Source/Resources/en.lproj/Localizable.strings index fdc2c4a3..532e449b 100644 --- a/Source/Resources/en.lproj/Localizable.strings +++ b/Source/Resources/en.lproj/Localizable.strings @@ -223,6 +223,8 @@ "i18n:userdef.kRespectClientAccentColor.shortTitle" = "Respect accent colors of the client app and the system"; "i18n:UserDef.kShiftEisuToggleOffTogetherWithCapsLock.description" = "This does: 1) On macOS 12 and later, if the Caps Lock gets turned off, then the internal switch for Shift-key / JIS-Eisu-key Alphanumerical Mode Toggle will also be switched off. 2) If you have turned off the same switch by JIS-Eisu key, then the Caps Lock gets turned off together. Note: vChewing has no way to recognize single hits of Shift key when Caps Lock is on."; "i18n:UserDef.kShiftEisuToggleOffTogetherWithCapsLock.shortTitle" = "Sync the off state between Caps Lock and Shift / Eisu Alphanumerical Toggle"; +"i18n:UserDef.kUseJKtoMoveCompositorCursorInCandidateState.description" = "⚠︎ This will automatically prevent J and K from being used as candidate keys. Also, this won't work with ‘%quick’ candidates in cassette mode."; +"i18n:UserDef.kUseJKtoMoveCompositorCursorInCandidateState.shortTitle" = "Use J / K to Move Buffer Cursor While Choosing Candidates"; "IBM" = "IBM"; "If disabled, this will insert space instead." = "If disabled, this will insert space instead."; "If not filling the weight, it will be 0.0, the maximum one. An ideal weight situates in [-9.5, 0], making itself can be captured by the walking algorithm. The exception is -114.514, the disciplinary weight. The walking algorithm will ignore it unless it is the unique result." = "If not filling the weight, it will be 0.0, the maximum one. An ideal weight situates in [-9.5, 0], making itself can be captured by the walking algorithm. The exception is -114.514, the disciplinary weight. The walking algorithm will ignore it unless it is the unique result."; diff --git a/Source/Resources/ja.lproj/Localizable.strings b/Source/Resources/ja.lproj/Localizable.strings index 6d7ec20b..655c4a0c 100644 --- a/Source/Resources/ja.lproj/Localizable.strings +++ b/Source/Resources/ja.lproj/Localizable.strings @@ -223,6 +223,8 @@ "i18n:userdef.kRespectClientAccentColor.shortTitle" = "客体アプリ・システムのアクセントカラーに準ず"; "i18n:UserDef.kShiftEisuToggleOffTogetherWithCapsLock.description" = "このチェックを入れると:イ)[macOS 12 以降] CapsLockがオフした時に、「Shiftキー・JIS英数キー」の共有していた内部のスイッチもオフにする;ロ)「JIS英数キー」でそのキーに応ずる英数モードがオフした時に、CapsLockもオフにする。尚、CapsLockがオンの時に、「Shiftキーだけを打った」行為は検知できません。"; "i18n:UserDef.kShiftEisuToggleOffTogetherWithCapsLock.shortTitle" = "「Shiftキー・JIS英数キー」による英数モードのオフ状態をCapsLockのオフ状態と同期する"; +"i18n:UserDef.kUseJKtoMoveCompositorCursorInCandidateState.description" = "⚠︎ これで「J / K」キーは言選り用キー陣列から自動的に外されます。尚、この機能はカセットモードの「%quick」による早速候補入力状態では利用できません。"; +"i18n:UserDef.kUseJKtoMoveCompositorCursorInCandidateState.shortTitle" = "候補陳列が見えるとき、「J / K」キーで入力緩衝列のカーソルを移す"; "IBM" = "IBM 配列"; "If disabled, this will insert space instead." = "未チェックの場合、スペースを挿入。"; "If not filling the weight, it will be 0.0, the maximum one. An ideal weight situates in [-9.5, 0], making itself can be captured by the walking algorithm. The exception is -114.514, the disciplinary weight. The walking algorithm will ignore it unless it is the unique result." = "優先度を記入しなかった場合、最高値「0」はデフォルト値です。理想な優先度の範囲は [-9.5, 0] 以内で、ウォーキング算法に捕まれられます。例外は懲戒値「-114.514」で、優先度を最低に極めた値であり、唯一結果のない場合、ウォーキング算法に無視されます。"; diff --git a/Source/Resources/zh-Hans.lproj/Localizable.strings b/Source/Resources/zh-Hans.lproj/Localizable.strings index 8f774092..2f832fae 100644 --- a/Source/Resources/zh-Hans.lproj/Localizable.strings +++ b/Source/Resources/zh-Hans.lproj/Localizable.strings @@ -223,6 +223,8 @@ "i18n:userdef.kRespectClientAccentColor.shortTitle" = "遵循客体应用或系统全局的强调色偏好设定"; "i18n:UserDef.kShiftEisuToggleOffTogetherWithCapsLock.description" = "勾选该选项后:1) [至少 macOS 12 起] Caps Lock 在关掉的时候,会连带关掉由 Shift 键 / JIS 英数键负责控制的英数模式;2) 在借由 JIS 英数键关掉其控制的英数模式的时候,也会关掉 Caps Lock。注:威注音无法在 Caps Lock 亮灯的时候感知 Shift 键的单独敲击事件。"; "i18n:UserDef.kShiftEisuToggleOffTogetherWithCapsLock.shortTitle" = "使「Shift 键 / JIS 英数键」英数模式的关闭状态与 Caps Lock 的关闭状态保持彼此同步"; +"i18n:UserDef.kUseJKtoMoveCompositorCursorInCandidateState.description" = "⚠︎ 这将自动禁止 J / K 键用作选字键。另外,该功能在磁带模式的“%quick”快速选字状态下无效。"; +"i18n:UserDef.kUseJKtoMoveCompositorCursorInCandidateState.shortTitle" = "在选字窗显示时允许以 J / K 键移动组字区游标"; "IBM" = "IBM 排列"; "If disabled, this will insert space instead." = "取消勾选的话,该按键会插入空格。"; "If not filling the weight, it will be 0.0, the maximum one. An ideal weight situates in [-9.5, 0], making itself can be captured by the walking algorithm. The exception is -114.514, the disciplinary weight. The walking algorithm will ignore it unless it is the unique result." = "不填写权重的话,预设权重为最高值「0」。理想的权重范围在 [-9.5, 0] 这个闭区间内,可以被爬轨算法自动抓到。例外就是惩戒权重「-114.514」,属于极端降权。这种情况下,除非是唯一结果,否则会被爬轨算法忽略掉。"; diff --git a/Source/Resources/zh-Hant.lproj/Localizable.strings b/Source/Resources/zh-Hant.lproj/Localizable.strings index cf3610ee..0b4e3db4 100644 --- a/Source/Resources/zh-Hant.lproj/Localizable.strings +++ b/Source/Resources/zh-Hant.lproj/Localizable.strings @@ -223,6 +223,8 @@ "i18n:userdef.kRespectClientAccentColor.shortTitle" = "遵循客體應用或系統全局的強調色偏好設定"; "i18n:UserDef.kShiftEisuToggleOffTogetherWithCapsLock.description" = "勾選該選項後:1) [至少 macOS 12 起] Caps Lock 在關掉的時候,會連帶關掉由 Shift 鍵 / JIS 英數鍵負責控制的英數模式;2) 在藉由 JIS 英數鍵關掉其控制的英數模式的時候,也會關掉 Caps Lock。註:威注音無法在 Caps Lock 亮燈的時候感知 Shift 鍵的單獨敲擊事件。"; "i18n:UserDef.kShiftEisuToggleOffTogetherWithCapsLock.shortTitle" = "使「Shift 鍵 / JIS 英数鍵」英數模式的關閉狀態與 Caps Lock 的關閉狀態保持彼此同步"; +"i18n:UserDef.kUseJKtoMoveCompositorCursorInCandidateState.description" = "⚠︎ 這將自動禁止 J / K 鍵用作選字鍵。另外,該功能在磁帶模式的「%quick」快速選字狀態下無效。"; +"i18n:UserDef.kUseJKtoMoveCompositorCursorInCandidateState.shortTitle" = "在選字窗顯示時允許以 J / K 鍵移動組字區游標"; "IBM" = "IBM 排列"; "If disabled, this will insert space instead." = "取消勾選的話,該按鍵會插入空格。"; "If not filling the weight, it will be 0.0, the maximum one. An ideal weight situates in [-9.5, 0], making itself can be captured by the walking algorithm. The exception is -114.514, the disciplinary weight. The walking algorithm will ignore it unless it is the unique result." = "不填寫權重的話,預設權重為最高值「0」。理想的權重範圍在 [-9.5, 0] 這個閉區間內,可以被爬軌算法自動抓到。例外就是懲戒權重「-114.514」,屬於極端降權。這種情況下,除非是唯一結果,否則會被爬軌算法忽略掉。";