diff --git a/Packages/vChewing_MainAssembly/Sources/MainAssembly/InputHandler/InputHandler_HandleCandidate.swift b/Packages/vChewing_MainAssembly/Sources/MainAssembly/InputHandler/InputHandler_HandleCandidate.swift index 87003bf0..5241381b 100644 --- a/Packages/vChewing_MainAssembly/Sources/MainAssembly/InputHandler/InputHandler_HandleCandidate.swift +++ b/Packages/vChewing_MainAssembly/Sources/MainAssembly/InputHandler/InputHandler_HandleCandidate.swift @@ -333,10 +333,24 @@ extension InputHandler { } } - // MARK: - Flipping pages by using symbol menu keys (when they are not occupied). + // MARK: - Calling Service Menu Key through "Shift+?" (if enabled). + + var candidateTextServiceMenuRunning: Bool { + state.node.containsCandidateServices && state.type == .ofSymbolTable + } + + serviceMenu: if prefs.useShiftQuestionToCallServiceMenu, input.commonKeyModifierFlags == .shift, input.text == "?" { + if candidateTextServiceMenuRunning { break serviceMenu } + let handled = handleServiceMenuInitiation( + candidateText: highlightedCandidate.value, + reading: highlightedCandidate.keyArray + ) + if handled { return true } + } + + // MARK: - Flipping pages or Calling Service Menu by the Symbol Menu Key. if input.isSymbolMenuPhysicalKey { - let candidateTextServiceMenuRunning = state.node.containsCandidateServices && state.type == .ofSymbolTable switch input.commonKeyModifierFlags { case .shift, [], .option where !candidateTextServiceMenuRunning: diff --git a/Packages/vChewing_MainAssembly/Sources/MainAssembly/Settings/SettingsCocoa/VwrSettingsPaneCocoaCandidates.swift b/Packages/vChewing_MainAssembly/Sources/MainAssembly/Settings/SettingsCocoa/VwrSettingsPaneCocoaCandidates.swift index 4bac5b01..206d34be 100644 --- a/Packages/vChewing_MainAssembly/Sources/MainAssembly/Settings/SettingsCocoa/VwrSettingsPaneCocoaCandidates.swift +++ b/Packages/vChewing_MainAssembly/Sources/MainAssembly/Settings/SettingsCocoa/VwrSettingsPaneCocoaCandidates.swift @@ -54,10 +54,15 @@ public extension SettingsPanesCocoa { UserDef.kMoveCursorAfterSelectingCandidate.render(fixWidth: innerContentWidth) UserDef.kUseDynamicCandidateWindowOrigin.render(fixWidth: innerContentWidth) UserDef.kDodgeInvalidEdgeCandidateCursorPosition.render(fixWidth: innerContentWidth) + UserDef.kUseShiftQuestionToCallServiceMenu + .render(fixWidth: innerContentWidth) { renderable in + renderable.currentControl?.target = self + renderable.currentControl?.action = #selector(self.performCandidateKeysSanityCheck(_:)) + } UserDef.kUseJKtoMoveCompositorCursorInCandidateState .render(fixWidth: innerContentWidth) { renderable in renderable.currentControl?.target = self - renderable.currentControl?.action = #selector(self.useJKToMoveBufferCursorDidSet(_:)) + renderable.currentControl?.action = #selector(self.performCandidateKeysSanityCheck(_:)) } }?.boxed() NSView() @@ -91,7 +96,7 @@ public extension SettingsPanesCocoa { window.callAlert(title: title.localized, text: explanation.localized) } - @IBAction func useJKToMoveBufferCursorDidSet(_: NSControl) { + @IBAction func performCandidateKeysSanityCheck(_: NSControl) { // 利用該變數的 didSet 屬性自糾。 PrefMgr.shared.candidateKeys = PrefMgr.shared.candidateKeys } diff --git a/Packages/vChewing_MainAssembly/Sources/MainAssembly/Settings/SettingsUI/VwrSettingsPaneCandidates.swift b/Packages/vChewing_MainAssembly/Sources/MainAssembly/Settings/SettingsUI/VwrSettingsPaneCandidates.swift index f6bbf6d5..b8881e96 100644 --- a/Packages/vChewing_MainAssembly/Sources/MainAssembly/Settings/SettingsUI/VwrSettingsPaneCandidates.swift +++ b/Packages/vChewing_MainAssembly/Sources/MainAssembly/Settings/SettingsUI/VwrSettingsPaneCandidates.swift @@ -41,6 +41,9 @@ public struct VwrSettingsPaneCandidates: View { @AppStorage(wrappedValue: false, UserDef.kUseJKtoMoveCompositorCursorInCandidateState.rawValue) private var useJKtoMoveCompositorCursorInCandidateState: Bool + @AppStorage(wrappedValue: true, UserDef.kUseShiftQuestionToCallServiceMenu.rawValue) + public var useShiftQuestionToCallServiceMenu: Bool + @AppStorage(wrappedValue: true, UserDef.kMoveCursorAfterSelectingCandidate.rawValue) private var moveCursorAfterSelectingCandidate: Bool @@ -72,6 +75,12 @@ public struct VwrSettingsPaneCandidates: View { .disabled(useRearCursorMode) } UserDef.kDodgeInvalidEdgeCandidateCursorPosition.bind($dodgeInvalidEdgeCandidateCursorPosition).render() + UserDef.kUseShiftQuestionToCallServiceMenu.bind( + $useShiftQuestionToCallServiceMenu.didChange { + // 利用該變數的 didSet 屬性自糾。 + PrefMgr.shared.candidateKeys = PrefMgr.shared.candidateKeys + } + ).render() UserDef.kUseJKtoMoveCompositorCursorInCandidateState.bind( $useJKtoMoveCompositorCursorInCandidateState.didChange { // 利用該變數的 didSet 屬性自糾。 diff --git a/Packages/vChewing_Shared/Sources/Shared/PrefMgr_Core.swift b/Packages/vChewing_Shared/Sources/Shared/PrefMgr_Core.swift index 71cbbdd2..b0c6a5e6 100644 --- a/Packages/vChewing_Shared/Sources/Shared/PrefMgr_Core.swift +++ b/Packages/vChewing_Shared/Sources/Shared/PrefMgr_Core.swift @@ -134,6 +134,9 @@ import SwiftExtension @AppProperty(key: UserDef.kUseJKtoMoveCompositorCursorInCandidateState.rawValue, defaultValue: false) public var useJKtoMoveCompositorCursorInCandidateState: Bool + @AppProperty(key: UserDef.kUseShiftQuestionToCallServiceMenu.rawValue, defaultValue: true) + public var useShiftQuestionToCallServiceMenu: Bool + @AppProperty(key: UserDef.kMoveCursorAfterSelectingCandidate.rawValue, defaultValue: true) public dynamic var moveCursorAfterSelectingCandidate: Bool diff --git a/Packages/vChewing_Shared/Sources/Shared/PrefMgr_Utilities.swift b/Packages/vChewing_Shared/Sources/Shared/PrefMgr_Utilities.swift index 83cc62b5..8abcc7b2 100644 --- a/Packages/vChewing_Shared/Sources/Shared/PrefMgr_Utilities.swift +++ b/Packages/vChewing_Shared/Sources/Shared/PrefMgr_Utilities.swift @@ -13,7 +13,10 @@ import SwiftExtension public extension PrefMgr { func validate(candidateKeys: String) -> String? { - let excluded = useJKtoMoveCompositorCursorInCandidateState ? "jk" : "" + var excluded = "" + if useJKtoMoveCompositorCursorInCandidateState { excluded.append("jk") } + if useShiftQuestionToCallServiceMenu { excluded.append("?") } + excluded.append(IMEApp.isKeyboardJIS ? "_" : "`~") return CandidateKey.validate(keys: candidateKeys, excluding: excluded) } } diff --git a/Packages/vChewing_Shared/Sources/Shared/Protocols/PrefMgrProtocol.swift b/Packages/vChewing_Shared/Sources/Shared/Protocols/PrefMgrProtocol.swift index 307b4f54..209c3245 100644 --- a/Packages/vChewing_Shared/Sources/Shared/Protocols/PrefMgrProtocol.swift +++ b/Packages/vChewing_Shared/Sources/Shared/Protocols/PrefMgrProtocol.swift @@ -32,6 +32,7 @@ public protocol PrefMgrProtocol { var shouldAutoReloadUserDataFiles: Bool { get set } var useRearCursorMode: Bool { get set } var useJKtoMoveCompositorCursorInCandidateState: Bool { get set } + var useShiftQuestionToCallServiceMenu: 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 a15cff9b..7dda910f 100644 --- a/Packages/vChewing_Shared/Sources/Shared/UserDef/UserDef.swift +++ b/Packages/vChewing_Shared/Sources/Shared/UserDef/UserDef.swift @@ -54,6 +54,7 @@ public enum UserDef: String, CaseIterable, Identifiable { case kShouldAutoReloadUserDataFiles = "ShouldAutoReloadUserDataFiles" case kUseRearCursorMode = "UseRearCursorMode" case kUseJKtoMoveCompositorCursorInCandidateState = "UseJKtoMoveCompositorCursorInCandidateState" + case kUseShiftQuestionToCallServiceMenu = "UseShiftQuestionToCallServiceMenu" case kUseDynamicCandidateWindowOrigin = "UseDynamicCandidateWindowOrigin" case kUseHorizontalCandidateList = "UseHorizontalCandidateList" case kChooseCandidateUsingSpace = "ChooseCandidateUsingSpace" @@ -180,6 +181,7 @@ public extension UserDef { case .kShouldAutoReloadUserDataFiles: return .bool case .kUseRearCursorMode: return .bool case .kUseJKtoMoveCompositorCursorInCandidateState: return .bool + case .kUseShiftQuestionToCallServiceMenu: return .bool case .kUseDynamicCandidateWindowOrigin: return .bool case .kUseHorizontalCandidateList: return .bool case .kChooseCandidateUsingSpace: return .bool @@ -344,6 +346,10 @@ public extension UserDef { shortTitle: "i18n:UserDef.kUseJKtoMoveCompositorCursorInCandidateState.shortTitle", description: "i18n:UserDef.kUseJKtoMoveCompositorCursorInCandidateState.description" ) + case .kUseShiftQuestionToCallServiceMenu: return .init( + userDef: self, + shortTitle: "i18n:UserDef.kUseShiftQuestionToCallServiceMenu.shortTitle" + ) 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 33b6b9fb..bbf3137d 100644 --- a/Source/Resources/en.lproj/Localizable.strings +++ b/Source/Resources/en.lproj/Localizable.strings @@ -250,6 +250,7 @@ "i18n:UserDef.kSpecifyCmdOptCtrlEnterBehavior.shortTitle" = "Command+Option+Ctrl+Enter:"; "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"; +"i18n:UserDef.kUseShiftQuestionToCallServiceMenu.shortTitle" = "Also use Shift+? to call Service Menu in Candidate Window"; "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 956be848..8553635a 100644 --- a/Source/Resources/ja.lproj/Localizable.strings +++ b/Source/Resources/ja.lproj/Localizable.strings @@ -250,6 +250,7 @@ "i18n:UserDef.kSpecifyCmdOptCtrlEnterBehavior.shortTitle" = "Command+Option+Ctrl+Enter:"; "i18n:UserDef.kUseJKtoMoveCompositorCursorInCandidateState.description" = "⚠︎ これで「J / K」キーは言選り用キー陣列から自動的に外されます。尚、この機能はカセットモードの「%quick」による早速候補入力状態では利用できません。"; "i18n:UserDef.kUseJKtoMoveCompositorCursorInCandidateState.shortTitle" = "候補陳列が見えるとき、「J / K」キーで入力緩衝列のカーソルを移す"; +"i18n:UserDef.kUseShiftQuestionToCallServiceMenu.shortTitle" = "候補陳列ウィンドウで「Shift+?」でもサービスメニューを呼出"; "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 e7126979..a57de2ce 100644 --- a/Source/Resources/zh-Hans.lproj/Localizable.strings +++ b/Source/Resources/zh-Hans.lproj/Localizable.strings @@ -250,6 +250,7 @@ "i18n:UserDef.kSpecifyCmdOptCtrlEnterBehavior.shortTitle" = "Command+Option+Ctrl+Enter:"; "i18n:UserDef.kUseJKtoMoveCompositorCursorInCandidateState.description" = "⚠︎ 这将自动禁止 J / K 键用作选字键。另外,该功能在磁带模式的“%quick”快速选字状态下无效。"; "i18n:UserDef.kUseJKtoMoveCompositorCursorInCandidateState.shortTitle" = "在选字窗显示时允许以 J / K 键移动组字区游标"; +"i18n:UserDef.kUseShiftQuestionToCallServiceMenu.shortTitle" = "允许在选字窗内也使用「Shift+?」叫出服务选单"; "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 d8b04858..061349f6 100644 --- a/Source/Resources/zh-Hant.lproj/Localizable.strings +++ b/Source/Resources/zh-Hant.lproj/Localizable.strings @@ -250,6 +250,7 @@ "i18n:UserDef.kSpecifyCmdOptCtrlEnterBehavior.shortTitle" = "Command+Option+Ctrl+Enter:"; "i18n:UserDef.kUseJKtoMoveCompositorCursorInCandidateState.description" = "⚠︎ 這將自動禁止 J / K 鍵用作選字鍵。另外,該功能在磁帶模式的「%quick」快速選字狀態下無效。"; "i18n:UserDef.kUseJKtoMoveCompositorCursorInCandidateState.shortTitle" = "在選字窗顯示時允許以 J / K 鍵移動組字區游標"; +"i18n:UserDef.kUseShiftQuestionToCallServiceMenu.shortTitle" = "允許在選字窗內也使用「Shift+?」叫出服務選單"; "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」,屬於極端降權。這種情況下,除非是唯一結果,否則會被爬軌算法忽略掉。";