SessionCtl // Make PCB, tooltip, and candidate window instances dynamic.
This commit is contained in:
parent
940e9b6d85
commit
a144e20304
|
@ -28,7 +28,7 @@ class SessionCtl: IMKInputController {
|
||||||
static var areWeNerfing = false
|
static var areWeNerfing = false
|
||||||
|
|
||||||
/// 目前在用的的選字窗副本。
|
/// 目前在用的的選字窗副本。
|
||||||
static var ctlCandidateCurrent: CtlCandidateProtocol = {
|
var ctlCandidateCurrent: CtlCandidateProtocol = {
|
||||||
let direction: NSUserInterfaceLayoutOrientation =
|
let direction: NSUserInterfaceLayoutOrientation =
|
||||||
PrefMgr.shared.useHorizontalCandidateList ? .horizontal : .vertical
|
PrefMgr.shared.useHorizontalCandidateList ? .horizontal : .vertical
|
||||||
if #available(macOS 10.15, *) {
|
if #available(macOS 10.15, *) {
|
||||||
|
@ -39,11 +39,11 @@ class SessionCtl: IMKInputController {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
/// 工具提示視窗的共用副本。
|
/// 工具提示視窗的副本。
|
||||||
static var tooltipInstance = TooltipUI()
|
var tooltipInstance = TooltipUI()
|
||||||
|
|
||||||
/// 浮動組字窗的共用副本。
|
/// 浮動組字窗的副本。
|
||||||
static var popupCompositionBuffer = PopupCompositionBuffer()
|
var popupCompositionBuffer = PopupCompositionBuffer()
|
||||||
|
|
||||||
// MARK: -
|
// MARK: -
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ extension SessionCtl: KeyHandlerDelegate {
|
||||||
return client.bundleIdentifier() ?? ""
|
return client.bundleIdentifier() ?? ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func candidateController() -> CtlCandidateProtocol { Self.ctlCandidateCurrent }
|
func candidateController() -> CtlCandidateProtocol { ctlCandidateCurrent }
|
||||||
|
|
||||||
func candidateSelectionCalledByKeyHandler(at index: Int) {
|
func candidateSelectionCalledByKeyHandler(at index: Int) {
|
||||||
candidatePairSelected(at: index)
|
candidatePairSelected(at: index)
|
||||||
|
|
|
@ -56,14 +56,14 @@ extension SessionCtl {
|
||||||
}()
|
}()
|
||||||
// 強制重新初期化,因為 NSAttributedTextView 有顯示滯後性。
|
// 強制重新初期化,因為 NSAttributedTextView 有顯示滯後性。
|
||||||
do {
|
do {
|
||||||
Self.tooltipInstance.hide()
|
tooltipInstance.hide()
|
||||||
Self.tooltipInstance = .init()
|
tooltipInstance = .init()
|
||||||
if state.type == .ofMarking {
|
if state.type == .ofMarking {
|
||||||
Self.tooltipInstance.setColor(state: state.data.tooltipColorState)
|
tooltipInstance.setColor(state: state.data.tooltipColorState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 再設定其文字顯示內容並顯示。
|
// 再設定其文字顯示內容並顯示。
|
||||||
Self.tooltipInstance.show(
|
tooltipInstance.show(
|
||||||
tooltip: tooltip, at: finalOrigin,
|
tooltip: tooltip, at: finalOrigin,
|
||||||
bottomOutOfScreenAdjustmentHeight: delta, direction: tooltipContentDirection
|
bottomOutOfScreenAdjustmentHeight: delta, direction: tooltipContentDirection
|
||||||
)
|
)
|
||||||
|
@ -74,34 +74,34 @@ extension SessionCtl {
|
||||||
state.isVerticalCandidateWindow = (isVerticalTyping || !PrefMgr.shared.useHorizontalCandidateList)
|
state.isVerticalCandidateWindow = (isVerticalTyping || !PrefMgr.shared.useHorizontalCandidateList)
|
||||||
|
|
||||||
/// 無論是田所選字窗還是 IMK 選字窗,在這裡都有必要重新初期化。
|
/// 無論是田所選字窗還是 IMK 選字窗,在這裡都有必要重新初期化。
|
||||||
Self.ctlCandidateCurrent.delegate = nil
|
ctlCandidateCurrent.delegate = nil
|
||||||
let candidateLayout: NSUserInterfaceLayoutOrientation =
|
let candidateLayout: NSUserInterfaceLayoutOrientation =
|
||||||
((isVerticalTyping || !PrefMgr.shared.useHorizontalCandidateList)
|
((isVerticalTyping || !PrefMgr.shared.useHorizontalCandidateList)
|
||||||
? .vertical
|
? .vertical
|
||||||
: .horizontal)
|
: .horizontal)
|
||||||
|
|
||||||
if #available(macOS 10.15, *) {
|
if #available(macOS 10.15, *) {
|
||||||
Self.ctlCandidateCurrent =
|
ctlCandidateCurrent =
|
||||||
PrefMgr.shared.useIMKCandidateWindow
|
PrefMgr.shared.useIMKCandidateWindow
|
||||||
? CtlCandidateIMK(candidateLayout) : CtlCandidateTDK(candidateLayout)
|
? CtlCandidateIMK(candidateLayout) : CtlCandidateTDK(candidateLayout)
|
||||||
if let candidateTDK = Self.ctlCandidateCurrent as? CtlCandidateTDK {
|
if let candidateTDK = ctlCandidateCurrent as? CtlCandidateTDK {
|
||||||
candidateTDK.maxLinesPerPage = isVerticalTyping ? 1 : 3
|
candidateTDK.maxLinesPerPage = isVerticalTyping ? 1 : 3
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Self.ctlCandidateCurrent = CtlCandidateIMK(candidateLayout)
|
ctlCandidateCurrent = CtlCandidateIMK(candidateLayout)
|
||||||
}
|
}
|
||||||
|
|
||||||
Self.ctlCandidateCurrent.candidateFont = Self.candidateFont(
|
ctlCandidateCurrent.candidateFont = Self.candidateFont(
|
||||||
name: PrefMgr.shared.candidateTextFontName, size: PrefMgr.shared.candidateListTextSize
|
name: PrefMgr.shared.candidateTextFontName, size: PrefMgr.shared.candidateListTextSize
|
||||||
)
|
)
|
||||||
|
|
||||||
if state.type == .ofAssociates {
|
if state.type == .ofAssociates {
|
||||||
Self.ctlCandidateCurrent.tooltip =
|
ctlCandidateCurrent.tooltip =
|
||||||
isVerticalTyping ? "⇧" : NSLocalizedString("Hold ⇧ to choose associates.", comment: "")
|
isVerticalTyping ? "⇧" : NSLocalizedString("Hold ⇧ to choose associates.", comment: "")
|
||||||
}
|
}
|
||||||
|
|
||||||
Self.ctlCandidateCurrent.useLangIdentifier = PrefMgr.shared.handleDefaultCandidateFontsByLangIdentifier
|
ctlCandidateCurrent.useLangIdentifier = PrefMgr.shared.handleDefaultCandidateFontsByLangIdentifier
|
||||||
Self.ctlCandidateCurrent.locale = {
|
ctlCandidateCurrent.locale = {
|
||||||
switch inputMode {
|
switch inputMode {
|
||||||
case .imeModeCHS: return "zh-Hans"
|
case .imeModeCHS: return "zh-Hans"
|
||||||
case .imeModeCHT:
|
case .imeModeCHT:
|
||||||
|
@ -115,25 +115,25 @@ extension SessionCtl {
|
||||||
|
|
||||||
if #available(macOS 10.14, *) {
|
if #available(macOS 10.14, *) {
|
||||||
// Spotlight 視窗會擋住 IMK 選字窗,所以需要特殊處理。
|
// Spotlight 視窗會擋住 IMK 選字窗,所以需要特殊處理。
|
||||||
if let ctlCandidateCurrent = Self.ctlCandidateCurrent as? CtlCandidateIMK {
|
if let ctlCandidateCurrent = ctlCandidateCurrent as? CtlCandidateIMK {
|
||||||
while ctlCandidateCurrent.windowLevel() <= client.windowLevel() {
|
while ctlCandidateCurrent.windowLevel() <= client.windowLevel() {
|
||||||
ctlCandidateCurrent.setWindowLevel(UInt64(max(0, client.windowLevel() + 1000)))
|
ctlCandidateCurrent.setWindowLevel(UInt64(max(0, client.windowLevel() + 1000)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Self.ctlCandidateCurrent.delegate = self // 會自動觸發田所選字窗的資料重載。
|
ctlCandidateCurrent.delegate = self // 會自動觸發田所選字窗的資料重載。
|
||||||
Self.ctlCandidateCurrent.visible = true
|
ctlCandidateCurrent.visible = true
|
||||||
|
|
||||||
if isVerticalTyping {
|
if isVerticalTyping {
|
||||||
Self.ctlCandidateCurrent.set(
|
ctlCandidateCurrent.set(
|
||||||
windowTopLeftPoint: NSPoint(
|
windowTopLeftPoint: NSPoint(
|
||||||
x: lineHeightRect().origin.x + lineHeightRect().size.width + 4.0, y: lineHeightRect().origin.y - 4.0
|
x: lineHeightRect().origin.x + lineHeightRect().size.width + 4.0, y: lineHeightRect().origin.y - 4.0
|
||||||
),
|
),
|
||||||
bottomOutOfScreenAdjustmentHeight: lineHeightRect().size.height + 4.0
|
bottomOutOfScreenAdjustmentHeight: lineHeightRect().size.height + 4.0
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Self.ctlCandidateCurrent.set(
|
ctlCandidateCurrent.set(
|
||||||
windowTopLeftPoint: NSPoint(x: lineHeightRect().origin.x, y: lineHeightRect().origin.y - 4.0),
|
windowTopLeftPoint: NSPoint(x: lineHeightRect().origin.x, y: lineHeightRect().origin.y - 4.0),
|
||||||
bottomOutOfScreenAdjustmentHeight: lineHeightRect().size.height + 4.0
|
bottomOutOfScreenAdjustmentHeight: lineHeightRect().size.height + 4.0
|
||||||
)
|
)
|
||||||
|
|
|
@ -173,7 +173,7 @@ extension SessionCtl {
|
||||||
// 這樣可以讓 interpretKeyEvents() 函式自行判斷:
|
// 這樣可以讓 interpretKeyEvents() 函式自行判斷:
|
||||||
// - 是就地交給 imkCandidates.interpretKeyEvents() 處理?
|
// - 是就地交給 imkCandidates.interpretKeyEvents() 處理?
|
||||||
// - 還是藉由 delegate 扔回 SessionCtl 給 KeyHandler 處理?
|
// - 還是藉由 delegate 扔回 SessionCtl 給 KeyHandler 處理?
|
||||||
if let imkCandidates = Self.ctlCandidateCurrent as? CtlCandidateIMK, imkCandidates.visible {
|
if let imkCandidates = ctlCandidateCurrent as? CtlCandidateIMK, imkCandidates.visible {
|
||||||
let event: NSEvent = CtlCandidateIMK.replaceNumPadKeyCodes(target: eventToDeal) ?? eventToDeal
|
let event: NSEvent = CtlCandidateIMK.replaceNumPadKeyCodes(target: eventToDeal) ?? eventToDeal
|
||||||
|
|
||||||
// Shift+Enter 是個特殊情形,不提前攔截處理的話、會有垃圾參數傳給 delegate 的 keyHandler 從而崩潰。
|
// Shift+Enter 是個特殊情形,不提前攔截處理的話、會有垃圾參數傳給 delegate 的 keyHandler 從而崩潰。
|
||||||
|
@ -207,7 +207,7 @@ extension SessionCtl {
|
||||||
|
|
||||||
private func imkCandidatesEventSubHandler(event: NSEvent) -> Bool {
|
private func imkCandidatesEventSubHandler(event: NSEvent) -> Bool {
|
||||||
let eventArray = [event]
|
let eventArray = [event]
|
||||||
guard let imkC = Self.ctlCandidateCurrent as? CtlCandidateIMK else { return false }
|
guard let imkC = ctlCandidateCurrent as? CtlCandidateIMK else { return false }
|
||||||
if event.isEsc || event.isBackSpace || event.isDelete || (event.isShiftHold && !event.isSpace) {
|
if event.isEsc || event.isBackSpace || event.isDelete || (event.isShiftHold && !event.isSpace) {
|
||||||
return commonEventHandler(event)
|
return commonEventHandler(event)
|
||||||
} else if event.isSymbolMenuPhysicalKey {
|
} else if event.isSymbolMenuPhysicalKey {
|
||||||
|
|
|
@ -22,9 +22,10 @@ extension SessionCtl {
|
||||||
state = newState
|
state = newState
|
||||||
switch state.type {
|
switch state.type {
|
||||||
case .ofDeactivated:
|
case .ofDeactivated:
|
||||||
Self.ctlCandidateCurrent.delegate = nil
|
ctlCandidateCurrent.delegate = nil
|
||||||
Self.ctlCandidateCurrent.visible = false
|
ctlCandidateCurrent.visible = false
|
||||||
Self.tooltipInstance.hide()
|
popupCompositionBuffer.hide()
|
||||||
|
tooltipInstance.hide()
|
||||||
if previous.hasComposition {
|
if previous.hasComposition {
|
||||||
commit(text: previous.displayedText)
|
commit(text: previous.displayedText)
|
||||||
}
|
}
|
||||||
|
@ -37,29 +38,29 @@ extension SessionCtl {
|
||||||
state = IMEState.ofEmpty()
|
state = IMEState.ofEmpty()
|
||||||
previous = state
|
previous = state
|
||||||
}
|
}
|
||||||
Self.ctlCandidateCurrent.visible = false
|
ctlCandidateCurrent.visible = false
|
||||||
Self.tooltipInstance.hide()
|
tooltipInstance.hide()
|
||||||
// 全專案用以判斷「.Abortion」的地方僅此一處。
|
// 全專案用以判斷「.Abortion」的地方僅此一處。
|
||||||
if previous.hasComposition, state.type != .ofAbortion {
|
if previous.hasComposition, state.type != .ofAbortion {
|
||||||
commit(text: previous.displayedText)
|
commit(text: previous.displayedText)
|
||||||
}
|
}
|
||||||
// 在這裡手動再取消一次選字窗與工具提示的顯示,可謂雙重保險。
|
// 在這裡手動再取消一次選字窗與工具提示的顯示,可謂雙重保險。
|
||||||
Self.ctlCandidateCurrent.visible = false
|
ctlCandidateCurrent.visible = false
|
||||||
Self.tooltipInstance.hide()
|
tooltipInstance.hide()
|
||||||
clearInlineDisplay()
|
clearInlineDisplay()
|
||||||
// 最後一道保險
|
// 最後一道保險
|
||||||
keyHandler.clear()
|
keyHandler.clear()
|
||||||
case .ofCommitting:
|
case .ofCommitting:
|
||||||
Self.ctlCandidateCurrent.visible = false
|
ctlCandidateCurrent.visible = false
|
||||||
Self.tooltipInstance.hide()
|
tooltipInstance.hide()
|
||||||
let textToCommit = state.textToCommit
|
let textToCommit = state.textToCommit
|
||||||
if !textToCommit.isEmpty { commit(text: textToCommit) }
|
if !textToCommit.isEmpty { commit(text: textToCommit) }
|
||||||
clearInlineDisplay()
|
clearInlineDisplay()
|
||||||
// 最後一道保險
|
// 最後一道保險
|
||||||
keyHandler.clear()
|
keyHandler.clear()
|
||||||
case .ofInputting:
|
case .ofInputting:
|
||||||
Self.ctlCandidateCurrent.visible = false
|
ctlCandidateCurrent.visible = false
|
||||||
Self.tooltipInstance.hide()
|
tooltipInstance.hide()
|
||||||
let textToCommit = state.textToCommit
|
let textToCommit = state.textToCommit
|
||||||
if !textToCommit.isEmpty { commit(text: textToCommit) }
|
if !textToCommit.isEmpty { commit(text: textToCommit) }
|
||||||
setInlineDisplayWithCursor()
|
setInlineDisplayWithCursor()
|
||||||
|
@ -67,27 +68,27 @@ extension SessionCtl {
|
||||||
show(tooltip: state.tooltip)
|
show(tooltip: state.tooltip)
|
||||||
}
|
}
|
||||||
case .ofMarking:
|
case .ofMarking:
|
||||||
Self.ctlCandidateCurrent.visible = false
|
ctlCandidateCurrent.visible = false
|
||||||
setInlineDisplayWithCursor()
|
setInlineDisplayWithCursor()
|
||||||
if state.tooltip.isEmpty {
|
if state.tooltip.isEmpty {
|
||||||
Self.tooltipInstance.hide()
|
tooltipInstance.hide()
|
||||||
} else {
|
} else {
|
||||||
show(tooltip: state.tooltip)
|
show(tooltip: state.tooltip)
|
||||||
}
|
}
|
||||||
case .ofCandidates, .ofAssociates, .ofSymbolTable:
|
case .ofCandidates, .ofAssociates, .ofSymbolTable:
|
||||||
Self.tooltipInstance.hide()
|
tooltipInstance.hide()
|
||||||
setInlineDisplayWithCursor()
|
setInlineDisplayWithCursor()
|
||||||
showCandidates()
|
showCandidates()
|
||||||
default: break
|
default: break
|
||||||
}
|
}
|
||||||
// 浮動組字窗的顯示判定
|
// 浮動組字窗的顯示判定
|
||||||
if state.hasComposition, PrefMgr.shared.clientsIMKTextInputIncapable.contains(clientBundleIdentifier) {
|
if state.hasComposition, PrefMgr.shared.clientsIMKTextInputIncapable.contains(clientBundleIdentifier) {
|
||||||
Self.popupCompositionBuffer.isTypingDirectionVertical = isVerticalTyping
|
popupCompositionBuffer.isTypingDirectionVertical = isVerticalTyping
|
||||||
Self.popupCompositionBuffer.show(
|
popupCompositionBuffer.show(
|
||||||
state: state, at: lineHeightRect(zeroCursor: true).origin
|
state: state, at: lineHeightRect(zeroCursor: true).origin
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Self.popupCompositionBuffer.hide()
|
popupCompositionBuffer.hide()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue